AGC030 简要题解

题目链接

第一次尝试做一整场AGC,结果做了一整天。。。还是开个博客记录一下吧。

A - Poisonous Cookies

一道难度评分高达90分的思博题。

#include<bits/stdc++.h>
using namespace std;
int a,b,c; 
int main(){
	scanf("%d%d%d",&a,&b,&c);
	printf("%d",b+min(c,a+b+1));
	return 0;
}

B - Tree Burning

一开始以为可以直接顺时针走一个,逆时针走一个,然后顺时针再走一个,逆时针再走一个,以此类推。然后观察样例可以发现,还可能会先沿某一个方向走若干个点,再开始上面的循环过程,这个贪心做法可以用数学归纳法证明是正确的。

于是接下来就枚举走到哪个点时开始循环,循环后的部分可以通过预处理前缀和与后缀和做到 \(\mathcal O(n)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
int L,n,x[N];
ll suf[N],pre[N];
int main(){
	scanf("%d%d",&L,&n);
	for(int i=1;i<=n;++i) scanf("%d",&x[i]);
	ll ans=0;
	for(int i=1;i<=n;++i) pre[i]=pre[i-1]+x[i];
	for(int i=n;i>=1;--i) suf[i]=suf[i+1]+L-x[i];
	for(int i=1;i<=n;++i){
		ll now=0;
		if((n-i+1)&1){
			int t=n-(n-i)/2+1;
			now+=2ll*suf[t]+2ll*(pre[t-1]-pre[i-1])-x[t-1];
		}
		else{
			int t=n-(n-i+1)/2+1;
			now+=2ll*suf[t]+2ll*(pre[t-1]-pre[i-1])-(L-x[t]); 
		}
		if(i==n) now=x[n];
		ans=max(ans,now);
	}
	for(int i=n;i>=1;--i){
		ll now=0;
		if(i&1){
			int t=i/2;
			now+=2ll*pre[t]+2ll*(suf[t+1]-suf[i+1])-(L-x[t+1]);
		}
		else{
			int t=i/2;;
			now+=2ll*pre[t]+2ll*(suf[t+1]-suf[i+1])-x[t]; 
		}
		if(i==1) now=L-x[1];
		ans=max(ans,now);
	}
	printf("%lld\n",ans);
	return 0;
}

C - Coloring Torus

神仙构造题。

如果 \(k\le 500\) 那么一个容易想到的做法是对每一行/每一列填同一个颜色,这样一定合法。考虑将这一个构造改装到对角线上(对角线是指 \(i+j\bmod n\) 相同的点)。但这样做依然只能染 \(n\) 个颜色。

如何变成 \(2n\) 个颜色呢?考虑对于每个对角线,给它分配两种颜色交替出现,比如下图中的 \(2,5\) 两种颜色,就可以通过此题。

1 2 3 4
2 3 5 1
3 4 1 2
5 1 2 3
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int k,n,a[N][N];
int main(){
	scanf("%d",&k);
	if(k<500){
		n=k;
		printf("%d\n",n);
		for(int i=1;i<=k;puts(""),++i) for(int j=1;j<=k;++j) printf("%d ",i);
		return 0;
	}
	else{
		n=500;k-=n;
		for(int i=1;i<=n;++i){
			int ta=i,tb=i;
			if(k) tb=n+i,k--;
			for(int x=1,y=i;x<=n;++x,y=y%n+1)
				a[x][y]=x&1?ta:tb;
		}
		printf("%d\n",n);
		for(int i=1;i<=n;puts(""),++i) for(int j=1;j<=n;++j) printf("%d ",a[i][j]);
	}
	return 0;
}

D - Inversion Sum

由于 \(n\) 很小,考虑直接对于位置 \(a_i,a_j\) 计算有多少种方案使得它们最终对答案做出贡献。

考虑倒着模拟整个过程,记 \(dp_{i,j}\) 表示所选择的两个数当前分别在位置 \(i\)\(j\) 上时,有多少种方案使得最终位置满足前一个数大于后一个数。初始 \(dp_{i,j}=[i>j]\)。然后倒着扫一遍所有交换操作 \((x,y)\),可以发现我们只用对包含 \(x,y\)\(\mathcal O(n)\) 个状态进行转移,这样一来复杂度就是 \(\mathcal O(nq)\) 的了。

最后再枚举 \(a_i,a_j\) ,根据它们的大小关系计算对答案的贡献即可。

#include<bits/stdc++.h>
using namespace std;
const int N=3010,mod=1e9+7,iv=(mod+1)/2;
int n,q,a[N],x[N],y[N],dp[N][N];
inline void inc(int &x,int y){x=(x+y>=mod)?x+y-mod:x+y;}
inline int add(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=1;i<=q;++i) scanf("%d%d",&x[i],&y[i]);
	for(int i=1;i<=n;++i)
		for(int j=1;j<i;++j)
			dp[i][j]=1;
	for(int i=q;i>=1;--i){
		int xx=x[i],yy=y[i];
		for(int j=1;j<=n;++j){
			if(j!=xx&&j!=yy){
				int a=dp[xx][j],b=dp[yy][j];inc(a,b);
				dp[xx][j]=dp[yy][j]=1ll*a*iv%mod;
				a=dp[j][xx],b=dp[j][yy];inc(a,b);
				dp[j][xx]=dp[j][yy]=1ll*a*iv%mod;
			}
		}
		int a=dp[xx][yy],b=dp[yy][xx];inc(a,b);
		dp[xx][yy]=dp[yy][xx]=1ll*a*iv%mod;
	}
	int ans=0;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j){
			if(i==j) continue; 
			if(a[i]<a[j]) ans=add(ans,dp[i][j]);
			else if(a[i]>a[j]) ans=add(ans,(mod+1-dp[i][j])%mod);
		}
	for(int i=1;i<q;++i) ans=add(ans,ans);
	printf("%d\n",ans);
	return 0;
}

E - Less than 3

神仙题,E比F难系列。考虑在序列上的 \(0,1\) 之间连一条红线,在 \(1,0\) 之间连一条蓝线,在序列的最左侧与最右侧分别增加若干条红蓝交替的线,那么一次操作就相当于将一条线左移或者右移,移动过程中要求不能有相邻的两条线的距离 \(>2\),下面是官方题解的图片:

于是我们只需要找一个 \(s\) 中的线与 \(t\) 中线的对应关系。找到关系后只需要固定位置不变的线,将其他线分成多个块,每个块内部向同一个方向移动即可完成构造。于是我们只需要 \(\mathcal O(n)\) 的枚举对应方案即可做到 \(\mathcal O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
const int N=5010;
char s[N],t[N];
int n,a[N],b[N],c1,c2;
int ans=0x3f3f3f3f;
int main(){
	scanf("%d",&n);
	scanf("%s%s",s+1,t+1);
	for(int i=1;i<n;++i) if(s[i]!=s[i+1]) a[++c1]=i;
	for(int i=1;i<n;++i) if(t[i]!=t[i+1]) b[++c2]=i;
	for(int i=-c2;i<=c1+1;++i){
		if((i&1)&&s[1]!=t[1]) continue;
		if(i%2==0&&s[1]==t[1]) continue; 
		int now=0;
		for(int j=min(1,i),k=min(1-(i-1),1);j<=c1||k<=c2;++j,++k){
			int x=(j<1)?0:(j>c1?n:a[j]);
			int y=(k<1)?0:(k>c2?n:b[k]);
			now+=abs(x-y);
		}
		ans=min(ans,now);
	}
	printf("%d\n",ans);
	return 0;
}

F - Permutation and Minimum

考虑将原问题看成将 \(1\sim 2n\) 的排列分成 \(n\) 对数。两个数已经确定的数对可以忽略,剩下还有确定了一个数以及一个都没有确定的数对。考虑 \(dp\),从小到大填入每一个数,设 \(dp_{i,j,k}\) 表示填了 \(\le i\) 的数,当前已确定最小值的有 \(j\) 对(设为 \(A\) 类数对), 已填一个数但不确定大小的有 \(k\) 个(设为 \(B\) 类数对)。

转移需要注意的是,如果将一个数放进 \(B\) 类数对,那我们无法确定是与哪一个数进行的匹配,可以将操作延后至遍历到与它配对的数时进行。遍历到某个被钦定了位置的数 \(x\) 时,如果还有 \(suf\) 个钦定数没有配对,那么就意味着有 \(suf-k\) 个前面的数与这些钦定的数进行匹配,因此可以讨论 \(x\) 是否与前面匹配,有转移:

\[dp_{i+1,j,k}\gets (suf-k)dp_{i,j,k}\\ dp_{i+1,j+1,k-1}\gets dp_{i,j,k} \]

于是复杂度为 \(\mathcal O(n^3)\)

#include<bits/stdc++.h>
using namespace std;
const int N=610,mod=1e9+7;
int dp[N][310][310];//考虑到第i个数字,当前已确定最小值的有j个, 已填一个数但未确定大小的有j个 
int n,a[N],d[N],tot,c1,c2,pd[N],vis[N],inv[N];
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 main(){
	scanf("%d",&n);n<<=1;
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		if(a[i]>0) pd[a[i]]=1;
	}
	inv[0]=1;
	for(int i=1;i<=n;++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1;i<=n;i+=2){
		if(a[i]>0&&a[i+1]>0){vis[a[i]]=vis[a[i+1]]=1;continue;}
		else if(a[i]>0||a[i+1]>0) ++c2;
		else ++c1;
	}
	for(int i=1;i<=n;++i) if(!vis[i]) d[++tot]=i;
	dp[0][0][c2]=1;
	int suf=c2;
	for(int i=0;i<tot;++i){
		for(int j=0;j<=c1+c2;++j)
			for(int k=0,x;k<=c2;++k){
				if(!(x=dp[i][j][k])) continue;
				if(pd[d[i+1]]){
					if(k!=suf) inc(dp[i+1][j][k],1ll*(suf-k)*x%mod);
					if(k) inc(dp[i+1][j+1][k-1],x);
				}
				else{
					if(j) inc(dp[i+1][j-1][k],x);
					int tmp=(i-j)/2+j+k;
					if(c1+c2-tmp) 
						inc(dp[i+1][j+1][k],x);
					if(k) inc(dp[i+1][j][k-1],x);
				}
			}
		if(pd[d[i+1]]) suf--;
	}
	int ans=dp[tot][0][0];
	for(int i=1;i<=c1;++i) ans=1ll*ans*i%mod;
	printf("%d\n",ans);
	return 0;
}

posted @ 2021-10-28 21:28  cjTQX  阅读(47)  评论(1编辑  收藏  举报