1. 程式人生 > 其它 >CSP-S2021 題解

CSP-S2021 題解

idea到用時方恨少

T1.廊橋分配

\(n^2\)暴力:直接列舉分配過程按照題意模擬。
對於每架飛機,設當前區域內廊橋總數為\(x\),那麼只有當\(x\)到達一定值時,它才有貢獻
考慮對於每個可能的\(x\),求出來它可以使得多少飛機有貢獻,那麼只要字首和一下再\(O(n)\)掃一遍就可以求得答案
思考怎麼求每個\(x\)的貢獻值,注意到體重說有一個先到先得的規定,也就是說當廊橋數量固定後,情況是唯一的,不存在方案優不優的問題
從簡單入手,每個飛機抽象稱一條線段,如果只有應該廊橋,那麼肯定是一開始不交的線段有貢獻
可以用set維護,每次相當於從前往後掃,不交的線段數量就是當前的\(x\)的貢獻數,如果到頭了從頭開始,\(x\)

要加一
由於每次一定會從集合裡刪去元素,所以算上lower_bound的複雜度上界是\(n\log n\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=100050;
int a1[N],a2[N],b1[N],b2[N];
int lsh[4*N],n,m1,m2,tot,cnt;
int ans1[N],ans2[N];
set <pair<int,int> >s;
signed main()
{
	//freopen("airport.in","r",stdin);
	//freopen("airport.out","w",stdout);
	cin>>n>>m1>>m2;
	for(int i=1;i<=m1;i++)scanf("%lld%lld",&a1[i],&a2[i]);
	for(int i=1;i<=m2;i++)scanf("%lld%lld",&b1[i],&b2[i]);
	for(int i=1;i<=m1;i++)lsh[i]=a1[i],lsh[m1+i]=a2[i];
	tot=m1*2;
	for(int i=1;i<=m2;i++)lsh[tot+i]=b1[i],lsh[tot+m2+i]=b2[i];
	tot+=m2*2;sort(lsh+1,lsh+1+tot);
	cnt=unique(lsh+1,lsh+1+tot)-lsh-1;
	for(int i=1;i<=m1;i++)
	{
		a1[i]=lower_bound(lsh+1,lsh+1+cnt,a1[i])-lsh;
		a2[i]=lower_bound(lsh+1,lsh+1+cnt,a2[i])-lsh;
	}
	for(int i=1;i<=m2;i++)
	{
		b1[i]=lower_bound(lsh+1,lsh+1+cnt,b1[i])-lsh;
		b2[i]=lower_bound(lsh+1,lsh+1+cnt,b2[i])-lsh;
	}
	for(int i=1;i<=m1;i++)s.insert(make_pair(a1[i],a2[i]));
	int ma=1e9,num=0;
	while(s.size())
	{
		auto ga=s.lower_bound(make_pair(ma,0));
		if(ga==s.end())
		{
			num++;ga=s.begin();
			ma=(*ga).second;s.erase(ga);
			ans1[num]++;continue;
		}
		ans1[num]++;ma=(*ga).second;
		s.erase(ga);
	}
	for(int i=1;i<=m2;i++)s.insert(make_pair(b1[i],b2[i]));
	ma=1e9;num=0;
	while(s.size())
	{
		auto ga=s.lower_bound(make_pair(ma,0));
		if(ga==s.end())
		{
			num++;ga=s.begin();
			ma=(*ga).second;s.erase(ga);
			ans2[num]++;continue;
		}
		ans2[num]++;ma=(*ga).second;
		s.erase(ga);
	}
	for(int i=1;i<=n;i++)
	{
		ans1[i]=ans1[i-1]+ans1[i];
		ans2[i]=ans2[i-1]+ans2[i];
	}
	int an=0;
	for(int i=0;i<=n;i++)an=max(an,ans1[i]+ans2[n-i]);
	cout<<an<<endl;
	return 0;
}

因為並不需要在值域上開陣列,所以離散化是不必要的

T2.括號序列

考場上直接棄了,這也反應了dp能力確實有待提高
本來是思考的dp,但想的一直是線性dp,最終沒能仔細理解題目,暴力也不大會
考慮區間dp,設計狀態是\(f_{l,r}\)\(g_{l,r}\)分別表示\(l,r\)這段區間合法,最左邊和最右邊的括號匹配/不匹配的方案數
為啥要設計這個狀態是因為避免重複,注意這裡匹配不匹配不是指是不是左右括號(否則直接不合法跳過),而是指最左邊和最右邊的是否能配成一對,還是他們分別和中間的什麼配成了對
預處理\(p_{l,r}\)表示一個區間內是否全是\(*\)或者\(?\),一個布林陣列,轉移時要用
轉移的主要思路就是看拼成的合法序列是他說的哪一種,對應不同轉移
邊界特判\(len=2\)

,兩端不合法直接continue,然後討論轉移方程
先是匹配的\(f\)陣列
當最終\((S)\)的時候,中間一定都是星號,就有

\[f_{l,r}+=[p_{l+1,r-1}](r-l-1<=k) \]

一定注意中間長度不能超過\(k\)
最終\((A)\)也類似,中間只要合法即可

\[f_{l,r}+=f_{l+1,r-1}+g_{l+1,r-1} \]

對於\((AS)\)\((SA)\)的轉移是類似的,列舉連續一段星號的個數\(O(n)\)轉移

\[(SA):f_{l,r}+=\sum_{i=1}^k (f_{l+i+1,r-1}+g_{l+i+1,r-1})[p_{l+1,l+i}] \]\[(AS):f_{l,r}+=\sum_{i=1}^k (f_{l+1,r-i-1}+g_{l+1,r-i-1})[p_{r-i,r-1}] \]

列舉的時候注意\(i\)的上界注意不要超,應該和右端點取min
剩下的\(ASB\)\(AB\)就是另一種情況\(g\)了,轉移要列舉中間\(S\)的長度,同時避免重複要欽定後面一定是匹配的

\[g_{l,r}+=\sum_{l<i<j<r,j-i-1<=k} ((f_{l,i}+g_{l,i})\times f_{j,r})[p_{i+1,j-1}] \]

這裡定義\(p_{x,x-1}\)也是1,便於轉移
發現最後一個暴力轉移是\(n^2\),直接寫應該是65分,考慮優化
發現最後在累加就是在統計一個前面有若干個星號,後面是一個匹配的序列的方案數,那麼可以直接把這個類似字尾和的陣列維護出來
\(dp_{l,r}\)表示上述陣列,那麼可以按照轉移\(SA\)類似的方法求出來

\[dp_{l,r}=\sum_{i=1}^k f_{l+i,r}[p_{l,l+i-1}] \]

此外所有的f方案都應該計入對應的dp方案之中,所以還要加上(因為上面的式子沒有算)
那麼最後轉移直接把對應\(j\)的列舉換成dp陣列就行了,總複雜度\(n^3\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=505;
const int mod=1e9+7;
int f[N][N],g[N][N],n,kk,ga[N][N];
bool p[N][N];char c[N];
signed main()
{
	cin>>n>>kk;scanf("%s",c+1);
	for(int i=1;i<=n;i++)
	{
		p[i][i-1]=1;
		for(int j=i;j<=n;j++)
		{
	 		bool fg=1;
	 		for(int k=i;k<=j;k++)if(c[k]!='?'&&c[k]!='*')
	 	  		{fg=0;break;}
	 		p[i][j]=fg;	
		}
	}
	for(int len=2;len<=n;len++)
	{
		for(int l=1;l<=n-len+1;l++)
		{
			int r=l+len-1;
			if((c[l]!='('&&c[l]!='?')||(c[r]!=')'&&c[r]!='?'))continue;
			if(len==2){f[l][r]=ga[l][r]=1;continue;}
			if(r-l-1<=kk)f[l][r]=(f[l][r]+p[l+1][r-1])%mod;
			f[l][r]=(f[l][r]+f[l+1][r-1]+g[l+1][r-1])%mod;
			for(int i=1;i<=kk&&l+1+i<=r-1;i++)if(p[l+1][l+i])f[l][r]=(f[l][r]+f[l+i+1][r-1]+g[l+i+1][r-1])%mod;
			for(int i=1;i<=kk&&r-i-1>=l+1;i++)if(p[r-i][r-1])f[l][r]=(f[l][r]+f[l+1][r-i-1]+g[l+1][r-i-1])%mod;
			for(int i=l+1;i<r;i++)g[l][r]=(g[l][r]+(f[l][i]+g[l][i])*ga[i+1][r]%mod)%mod;
		}
		for(int l=1;l<=n-len+1;l++)
		{
			int r=l+len-1;if(c[r]!=')'&&c[r]!='?')continue;
			ga[l][r]=f[l][r];
			for(int i=1;i<=kk&&l+i<=r;i++)if(p[l][l+i-1])ga[l][r]=(ga[l][r]+f[l+i][r])%mod;				
		}
	}
	cout<<(f[1][n]+g[1][n])%mod<<endl;
	return 0;
}

T3.迴文

資料範圍啟發正解的時間複雜度應該就是\(O(n)\),帶\(log\)的可能性不是很大
觀察樣例會發現,已選數字剩餘位置一定是連續的,因此可以以這個進行貪心
反證如果不連續,那麼兩個位置一定會擋住一些數字,使得最後一定形不成迴文
那麼維護幾個指標開始掃,優先選左邊保證字典序,當選出\(n\)個數之後利用迴文性質反推出剩下\(n\)個操作都是什麼
一個細節是不能選擇重複數字,當已經選擇的和剩餘位置捱上需要特判一下,例子可以看大樣例98組兩個一起的17

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=500050;
int n,a[2*N],b[2*N],tot;
int ans[2*N];vector <int> p[2*N];
inline void clear()
{
	for(int i=1;i<=2*n;i++)p[i].clear(),a[i]=b[i]=ans[i]=0;
}
inline void gan(int l,int r)
{
	for(int i=1;i<=n;i++)
	{
		if(a[l]==b[n-i+1])l++,ans[++tot]=0;
		else r--,ans[++tot]=1;
	}
	for(int i=1;i<=2*n;i++)
	{	
		if(ans[i])putchar('R');
		else putchar('L');
	}
	puts("");
}
signed main()
{
	//freopen("palin.in","r",stdin);
	//freopen("palin.out","w",stdout);
	int t;cin>>t;
	while(t--)
	{
		clear();scanf("%lld",&n);tot=0;
		for(int i=1;i<=2*n;i++)scanf("%lld",&a[i]);
		for(int i=1;i<=2*n;i++)p[a[i]].push_back(i);
		int ga=p[a[1]][0];if(ga==1)ga=p[a[1]][1];
		int pl=ga-1,pr=ga+1;
		int l=2,r=2*n;b[++tot]=a[1],ans[tot]=0;
		for(int i=2;i<=n;i++)
		{
			bool ok=0;
			int p1=p[a[l]][0];if(p1==l)p1=p[a[l]][1];
			int p2=p[a[r]][0];if(p2==r)p2=p[a[r]][1];
			if(p1==pl&&l<pl)b[++tot]=a[l++],ans[tot]=0,pl--,ok=1;
			else if(p1==pr)b[++tot]=a[l++],ans[tot]=0,pr++,ok=1;
			else if(p2==pl)b[++tot]=a[r--],ans[tot]=1,pl--,ok=1;
			else if(p2==pr&&r>pr)b[++tot]=a[r--],ans[tot]=1,pr++,ok=1;
			if(!ok){tot=0;break;}
		}
		if(tot==n){gan(l,r);continue;}
		ga=p[a[2*n]][0];if(ga==2*n)ga=p[a[2*n]][1];
		pl=ga-1,pr=ga+1;
		l=1,r=2*n-1;b[++tot]=a[2*n],ans[tot]=1;
		for(int i=2;i<=n;i++)
		{
			bool ok=0;
			int p1=p[a[l]][0];if(p1==l)p1=p[a[l]][1];
			int p2=p[a[r]][0];if(p2==r)p2=p[a[r]][1];
			if(p1==pl&&l<pl)b[++tot]=a[l++],ans[tot]=0,pl--,ok=1;
			else if(p1==pr)b[++tot]=a[l++],ans[tot]=0,pr++,ok=1;
			else if(p2==pl)b[++tot]=a[r--],ans[tot]=1,pl--,ok=1;
			else if(p2==pr&&r>pr)b[++tot]=a[r--],ans[tot]=1,pr++,ok=1;
			if(!ok){tot=0;break;}
		}
		if(!tot){puts("-1");continue;}
		gan(l,r);
	}
	return 0;
}

T4.交通規劃

正解看不懂,寫了個複雜對不太正確的網路流做法
黑白染色,格子裡面的點直接連雙向邊,格子外面的白點從源點向其連邊,連到最近的點,黑點由最近的點向他連邊,連到匯點
發現這樣答案就是最小割,即選擇代價最少的邊刪掉把黑白割斷,因此源匯點都是INF
理論是60-80,但是機房大佬都A了,人傻常數大卡不過去,洛谷資料最高95

#include <bits/stdc++.h>
using namespace std;
const int N=505;
inline int read()
{
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
struct node{
	int to,next,w;
}a[(int)2e6];
int head[N*N],mm=2;
inline void add(int x,int y,int w)
{
	a[mm].to=y;a[mm].w=w;
	a[mm].next=head[x];head[x]=mm++;
}
inline void gan(int x,int y,int z)
{
	add(x,y,z);add(y,x,0);
}
int s,t,hd[N*N],d[N*N];
inline bool bfs()
{
	memset(d,0x3f,sizeof(d));
	memcpy(head,hd,sizeof(hd));
	std::queue <int> q;
	q.push(s);d[s]=0;
	while(q.size())
	{
		int x=q.front();q.pop();
		for(int i=head[x];i;i=a[i].next)
		{
			if(!a[i].w)continue;
			int y=a[i].to;
			if(d[y]>d[x]+1)d[y]=d[x]+1,q.push(y);
		}	
		if(x==t)return 1;
	}
	return 0;
}
int dfs(int x,int flow)
{
	if(x==t)return flow;
	int rest=flow,k;
	for(int i=head[x];i;head[x]=i=a[i].next)
	{
		if(!a[i].w)continue;
		int y=a[i].to;
		if(d[y]==d[x]+1)
		{
			k=dfs(y,min(a[i].w,rest));
			if(!k)d[y]=0;
			else a[i].w-=k,a[i^1].w+=k,rest-=k;
		}
		if(!rest)break;
	}
	return flow-rest;
}
int n,m,T,w1[N][N],w2[N][N],mp[N][N],tot;
inline void clear()
{
	mm=2;memset(head,0,sizeof(head));
}
signed main()
{
	cin>>n>>m>>T;
	for(int i=1;i<=n-1;i++)for(int j=1;j<=m;j++)w1[i][j]=read();
	for(int i=1;i<=n;i++)for(int j=1;j<=m-1;j++)w2[i][j]=read();
	s=++tot,t=++tot;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)mp[i][j]=++tot;
	while(T--)
	{
		clear();int k=read(),cnt=tot;
		for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
		{
			if(i>1)gan(mp[i][j],mp[i-1][j],w1[i-1][j]);
			if(i<n)gan(mp[i][j],mp[i+1][j],w1[i][j]);
			if(j>1)gan(mp[i][j],mp[i][j-1],w2[i][j-1]);
			if(j<m)gan(mp[i][j],mp[i][j+1],w2[i][j]);
		}
		for(int i=1;i<=k;i++)
		{
			int w=read(),x=read(),op=read(),id;
			if(x>=1&&x<=m)id=mp[1][x];
			else if(x>m&&x<=m+n)id=mp[x-m][m];
			else if(x>m+n&&x<=m*2+n)id=mp[n][m-(x-(m+n))+1];
			else id=mp[n-(x-(2*m+n))+1][1];
			if(op)gan(s,++cnt,1e9),gan(cnt,id,w);
			else gan(++cnt,t,1e9),gan(id,cnt,w);
		}		
		memcpy(hd,head,sizeof(hd));
		int ans=0;
		while(bfs())ans+=dfs(s,1e9);
		printf("%d\n",ans);
	}
	return 0;
}
予明日所有失敗者 賦萬千不滅頌歌