廊橋分配
description
機場分國內區和國際區,分別有\(m_1,m_2\) 架飛機會到來,每架飛機停在機場的時間為\([a_i,b_i]\) 。每架飛機來到機場后會選擇在廊橋/遠機位。飛機會優先停靠廊橋,而廊橋使用先到先得,即如果某架飛機到達時存在空閑的廊橋則會停靠,否則停靠遠機位。現在總共\(n\) 個廊橋,要求進行合理的分配使得停靠廊橋的飛機盡量多。\(1\le n,m_1+m_2\le 10^5\)
solution
將國內區和國際區分開考慮,最后再將答案合並。容易發現對於一架飛機如果在有\(i\) 個廊橋時找到了位置,那么有\(>i\) 個廊橋時它也能找到位置並且位置不變。因此從小到大枚舉廊橋個數,每次根據先到先得的原則選擇不相交的區間即可。使用set進行維護,復雜度\(\mathcal O(n\log n)\) 。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m[2],res[2][N];
set<pair<int,int> >s[2];
int main()
{
scanf("%d%d%d",&n,&m[0],&m[1]);
for(bool o:{0,1})
for(int i=1,l,r;i<=m[o];++i)
scanf("%d%d",&l,&r),s[o].insert(make_pair(l,r));
for(bool o:{0,1})
for(int i=1;i<=n;++i)
{
res[o][i]=res[o][i-1];
if(s[o].empty())continue;
int pre=0;
while(1)
{
auto p=s[o].upper_bound(make_pair(pre,0));
if(p==s[o].end())break;
++res[o][i];pre=(*p).second;
s[o].erase(p);
}
}
int ans=0;
for(int i=0;i<=n;++i)ans=max(ans,res[0][i]+res[1][n-i]);
printf("%d\n",ans);
return 0;
}
括號序列
description
以如下的方式定義“超級括號序列”:
()、(S)均是符合規范的超級括號序列,其中S表示任意一個僅由不超過 k 個字符*組成的非空字符串(以下兩條規則中的S均為此含義);- 如果字符串
A和B均為符合規范的超級括號序列,那么字符串AB、ASB均為符合規范的超級括號序列,其中AB表示把字符串A和字符串B拼接在一起形成的字符串; - 如果字符串
A為符合規范的超級括號序列,那么字符串(A)、(SA)、(AS)均為符合規范的超級括號序列。 - 所有符合規范的超級括號序列均可通過上述 3 條規則得到。
現在需要對一個包含且僅包含(,),?,*的字符串\(|S|\) 求出有多少中填法使得其得到的字符串是一個超級括號序列。
有模數。\(|S|\le 500\) 。
solution
區間dp即可。三種規則生成的字符串都是互不重復的,因此對三種規則分別轉移即可。直接轉移會在第二個規則時將\(\texttt{()()()}\) 這種情況多次計算,因此在區間\([l,r]\) 枚舉斷點\(k\) 時必須欽定\([l,k]\) 這段區間\(l\) 處的左括號和\(k\) 處的右括號匹配即可,需要新增一種dp狀態。再使用前綴和進行優化就能做到\(\mathcal O(n^3)\) 了。
code
#include<bits/stdc++.h>
using namespace std;
const int N=505,mod=1e9+7;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline void inc(int&x,int y){x=x+y>=mod?x+y-mod:x+y;}
int n,k,f[N][N],s[N],g[N][N],sum[N][N];char ch[N];
inline bool ok(int l,int r){return s[r]-s[l-1]==0;}
inline bool okl(int p){return ch[p]=='?'||ch[p]=='(';}
inline bool okr(int p){return ch[p]=='?'||ch[p]==')';}
inline int S(int pl,int pr,int r){return dec(sum[pl][r],sum[pr+1][r]);}
int main()
{
scanf("%d%d",&n,&k);
scanf("%s",ch+1);
for(int i=1;i<=n;++i)s[i]=s[i-1]+(ch[i]=='('||ch[i]==')');
for(int len=2;len<=n;++len)
for(int l=1,r=l+len-1;r<=n;++l,++r)
{
int&d=f[l][r],&e=g[l][r];
if(len==2&&okl(l)&&okr(r))++d,++e;
if(3<=len&&len<=k+2&&okl(l)&&okr(r)&&ok(l+1,r-1))++d,++e;
for(int p=l,q=p;p<r;++p)
{
inc(d,1ll*g[l][p]*f[p+1][r]%mod);
q=max(q,p);
while(q<r&&ok(q+1,q+1))++q;
if(p+2<=r&&p<q)inc(d,1ll*g[l][p]*S(p+2,min({r,q+1,p+k+1}),r)%mod);
}
if(len>2&&okl(l)&&okr(r))
{
int res=0;
inc(res,f[l+1][r-1]);
for(int t=1;t<=k&&l+t<r;++t)
if(ok(l+1,l+t))inc(res,f[l+t+1][r-1]);
else break;
for(int t=1;t<=k&&r-t>l;++t)
if(ok(r-t,r-1))inc(res,f[l+1][r-t-1]);
else break;
inc(d,res),inc(e,res);
}
sum[l][r]=add(sum[l+1][r],d);
}
printf("%d\n",f[1][n]);
return 0;
}
回文
description
有一個長度為\(2n\) 的雙端隊列,其中\(1\sim n\) 都恰好出現了2次。現在要求每次操作從左側/右側彈出一個數依次排列形成序列\(b\) ,要求\(b\) 是回文的。如果無法做到,輸出-1,否則輸出字典序最小的方案數。\(n\le 5\times 10^5\)
solution
顯然必然存在一個分界點滿足分界點左側都是從左端依次彈出,分界點右側都是從右端依次彈出。枚舉這個分界點,然后左右兩側的數各形成一個棧。每次需要從棧頂取出元素\(x\) ,那么必須滿足和\(x\) 值相同的元素必須位於某個棧的棧底。然后將這兩個數都刪去繼續做即可。由於要求字典序最小,因此每次需要貪心的選擇。然而現在仍然是\(\mathcal O(n^2)\) 的。注意到兩個棧的棧頂都是確定的,因此對應的棧底也有強限制,這使得合法的分界點只有4個,分別枚舉做即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,a[N],s[N],t[N];bool ans[N],fl,res[N];
inline void work(int p)
{
int x=0,y=0;
for(int i=1;i<=p;++i)s[++x]=a[i];
for(int i=2*n;i>p;--i)t[++y]=a[i];
int c=1,d=1;
for(int i=1;i<=n;++i)
{
int p1=i,p2=2*n-i+1;
if(c<x&&s[c]==s[x])res[p1]=res[p2]=0,++c,--x;
else if(c<=x&&d<=y&&s[c]==t[y])res[p1]=0,res[p2]=1,++c,--y;
else if(c<=x&&d<=y&&t[d]==s[x])res[p1]=1,res[p2]=0,++d,--x;
else if(d<y&&t[d]==t[y])res[p1]=res[p2]=1,++d,--y;
else return;
}
fl=1;bool ok=0;
for(int i=1;i<=2*n;++i)
if(res[i]<ans[i]){ok=1;break;}
else if(res[i]>ans[i]){ok=0;break;}
if(ok)for(int i=1;i<=2*n;++i)ans[i]=res[i];
}
int main()
{
int T=read();
while(T-->0)
{
n=read();
for(int i=1;i<=2*n;++i)a[i]=read(),ans[i]=1;
int p1=0,p2=0;
for(int i=2;i<=2*n;++i)if(a[i]==a[1]){p1=i;break;}
for(int i=1;i<2*n;++i)if(a[i]==a[2*n]){p2=i;break;}
fl=0;work(p1),work(p1-1);work(p2),work(p2-1);
if(!fl){puts("-1");continue;}
for(int i=1;i<=2*n;++i)putchar(ans[i]?'R':'L');puts("");
}
return 0;
}
交通規划
description
有一個由\(n\) 條橫直線,\(m\) 條縱直線互相相交形成的網格圖,網格中的邊有邊權。
現在從網格邊界向外延伸的\(2(n+m)\) 條射線中,有\(k\) 條出現了顏色為黑/白的點,並且一直它和最近的網格點之間的邊權大小。現在需要給網格點進行染色,使得左右兩端染色不同的邊的邊權之和最小。
\(n,m\le 500,\sum k\le 50\)
solution
\(k=2\) 的時候容易發現是最小割。根據狼抓兔子的那套理論,可以將平面圖最小割轉化為對偶圖最短路。
\(k>2\) 的時候考慮其本質是什么,如下圖所示:

對於相鄰且顏色不同的點我們需要用割邊將其隔開,而注意到一條割邊可以分隔兩對這樣的點,因此對這些間隔(圖中綠色部分)一一匹配可以達到最優。而匹配的代價就是原圖最小割,即對偶圖上的最短路。

而對於兩條割線相交的情況(上圖黑線),我們可以用藍線進行代替,代價相同且效果相同。即我們可以通過調整使得最優解的任意兩條割線都不相交。
因此對於每組詢問只需要處理出間隔間的最短路,然后區間dp即可。dp轉移時只需考慮左側端點的匹配節點就行。
時間復雜度:\(\mathcal O(k^3+knm\log nm)\) 。
code
#include<bits/stdc++.h>
using namespace std;
const int N=505,inf=0x3f3f3f3f;
int n,m,T,k;
int tot=1,fi[N*N],ne[N*N*4],to[N*N*4],w[N*N*4],op[N*4];
struct node{int pos,val,c;}t[N];
inline int id(int x,int y){return x*(m+1)+y+1;}
inline void add(int x,int y,int s)
{
ne[++tot]=fi[x],fi[x]=tot,to[tot]=y,w[tot]=s;
ne[++tot]=fi[y],fi[y]=tot,to[tot]=x,w[tot]=s;
}
int key[N*8],dis[N*N],res[105][105],f[105][105];bool done[N*N];
inline int gp(int x)
{
if(x<=m)return id(0,x);x-=m;
if(x<=n)return id(x,m);x-=n;
if(x<=m)return id(n,m-x);x-=m;
return id(n-x,0);
}
inline void dij(int S)
{
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>,greater<pii> >q;
fill(dis+1,dis+id(n,m)+1,inf);
fill(done+1,done+id(n,m)+1,0);
q.push(make_pair(dis[S]=0,S));
while(!q.empty())
{
int u=q.top().second;q.pop();
if(done[u])continue;done[u]=1;
for(int i=fi[u];i;i=ne[i])
{
int v=to[i];
if(dis[v]>dis[u]+w[i])
{
dis[v]=dis[u]+w[i];
q.push(make_pair(dis[v],v));
}
}
}
}
inline void work()
{
scanf("%d",&k);int cc=0;
for(int i=1;i<=k;++i)
scanf("%d%d%d",&t[i].val,&t[i].pos,&t[i].c);
sort(t+1,t+k+1,[&](const node&x,const node&y){return x.pos<y.pos;});
for(int i=1;i<=k;++i)
{
int now=t[i].pos;
w[op[now]]=w[op[now]^1]=t[i].val;
int j=i==k?1:i+1;
if(t[i].c==t[j].c)continue;
key[++cc]=gp(t[i].pos);
}
if(!cc)puts("0");
else
{
for(int i=1;i<=cc;++i)
{
dij(key[i]);
for(int j=1;j<=cc;++j)
res[i][j]=dis[key[j]];
}
for(int i=1,j=cc+1;i<=cc;++i,++j)key[j]=key[i];
cc<<=1;int mm=cc>>1;
for(int i=1;i<=cc;++i)f[i][i-1]=0;
for(int len=2;len<=cc;len+=2)
for(int l=1,r=l+len-1;r<=cc;++l,++r)
{
int&d=f[l][r];d=inf;
for(int t=l+1;t<=r;t+=2)
d=min(d,f[l+1][t-1]+f[t+1][r]+res[l>mm?l-mm:l][t>mm?t-mm:t]);
}
int ans=inf;
for(int i=1,j=mm;j<=cc;++i,++j)ans=min(ans,f[i][j]);
printf("%d\n",ans);
}
for(int i=1;i<=k;++i)
{
int now=t[i].pos;
w[op[now]]=w[op[now]^1]=0;
}
}
int main()
{
scanf("%d%d%d",&n,&m,&T);
for(int i=1;i<n;++i)
for(int j=1,t;j<=m;++j)
scanf("%d",&t),add(id(i,j-1),id(i,j),t);
for(int i=1;i<=n;++i)
for(int j=1,t;j<m;++j)
scanf("%d",&t),add(id(i-1,j),id(i,j),t);
for(int i=1;i<=m;++i)add(id(0,i-1),id(0,i),0),op[i]=tot;
for(int i=1;i<=n;++i)add(id(i-1,m),id(i,m),0),op[m+i]=tot;
for(int i=1;i<=m;++i)add(id(n,m-i+1),id(n,m-i),0),op[m+n+i]=tot;
for(int i=1;i<=n;++i)add(id(n-i+1,0),id(n-i,0),0),op[m+n+m+i]=tot;
while(T--)work();
return 0;
}
