CSP-S2021題解


廊橋分配

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

以如下的方式定義“超級括號序列”:

  1. ()(S) 均是符合規范的超級括號序列,其中 S 表示任意一個僅由不超過 k 字符 * 組成的非空字符串(以下兩條規則中的 S 均為此含義);
  2. 如果字符串 AB 均為符合規范的超級括號序列,那么字符串 ABASB 均為符合規范的超級括號序列,其中 AB 表示把字符串 A 和字符串 B 拼接在一起形成的字符串;
  3. 如果字符串 A 為符合規范的超級括號序列,那么字符串 (A)(SA)(AS) 均為符合規范的超級括號序列。
  4. 所有符合規范的超級括號序列均可通過上述 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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM