其他分享
首页 > 其他分享> > AGC056 题解

AGC056 题解

作者:互联网

更好的阅读体验

Fun fact: For all AGC problems with score>=1600 this year, the number of accepted solutions is no more than 1. --Retired_MiFaFaOvO

A. Three Cells per Row and Column


最简单构造题?

不考虑第二个限制,可以这样染:

#.....##
##.....#
###.....
.###....
...###..
....###.
.....###

对于第二个限制,考虑换一下行的顺序。

可以想到让每一行都形成一个联通块(前两行共同组成 \(2\) 个联通块),所以就是:

#.....##
##.....#
..###...
.....###
.###....
....###.
###.....
...###..

\(n=6\) 或 \(n=7\) 时会有点小问题,实测把第 \(2\) 行和第 \(4\) 行换一下即可。


代码里是按列写的。

#include<bits/stdc++.h>
using namespace std;
const int N=550;
int n,ans[N][N];

int main(){
	cin>>n;
	ans[1][1]=ans[1][2]=ans[2][2]=1;
	ans[n][1]=ans[n][2]=ans[n-1][1]=1;
	int nw=2;
	for(int st=3;st>=1;st--){
		for(int x=st;x+2<=n;x+=3){
			nw++;
			ans[x][nw]=ans[x+1][nw]=ans[x+2][nw]=1;
		}
	}
	if(n==6 || n==7){
		for(int i=1;i<=n;i++) swap(ans[i][2],ans[i][4]);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(ans[i][j]) putchar('#');
			else putchar('.');
		}
		putchar('\n');
	}
}

B. Range Argmax


场上想了两小时没整出来

考虑直接数 \(x\),要求等价于若 \(i\) 和 \(j\) 区间分别选了 \(a\) 和 \(b\) 作为最大值且 \(a,b \in [L_i,R_i] \wedge [L_j,R_j]\),那么 \(a=b\)。然后就不会了

转换思路,考虑数 \(p\),但是为了避免算重我们让一个 \(x\) 只对应一个 \(p\)。

考虑怎样由 \(x\) 构造一个 \(p\),可以从大到小插入每个值,将其插入能插入的 最左端 的位置。

这样构造并没有什么性质能够快速判断 \(x\) 能否生成 \(p\)。但是我们发现每次插入一个值后,跨过该位置的区间的最大值已经被定下来了,而左右区间是独立的,可以考虑区间 DP。

对于区间 \([l,r]\),枚举它的最大值位置为 \(k\),即将它分成了 \([l,k)\) 和 \((k,r]\) 两部分。然而,为了确保不算重,我们需要保证 \(k\) 不能放在更左边的位置

于是设 \(f_{l,r,k}\) 表示区间 \([l,r]\) 的最大值位置为 \(k\) 时的答案,可以枚举左区间最大值位置 \(t\) 转移,时间复杂度 \(O(n^4)\)。

至于判断是否有区间同时包含 \(t\) 与 \(k\),也可以用一些高超的写法做到 \(O(n^3)\)。

使用前缀和优化,总时间复杂度 \(O(n^3)\)。

所以你管这叫 B(900) ???


#include<bits/stdc++.h>
using namespace std;
const int N=330,mod=998244353;
int n,m,ans;
int G[N][N],sG[N][N],pre[N][N],f[N][N][N],sf[N][N][N];

int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int l,r;
		cin>>l>>r;
		G[l][r]=1;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			sG[i][j]=sG[i][j-1]+G[i][j];
	for(int len=1;len<=n;len++){
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1,p=l;
			for(int k=l;k<=r;k++){
				int tot=(k==r)+(sf[k+1][r][r]+mod-sf[k+1][r][k])%mod;
				
				while(p<=r && sG[p][r]-sG[p][k-1]==0) p++;
				
				if(p<k) f[l][r][k]=(f[l][r][k]+(sf[l][k-1][k-1]+mod-sf[l][k-1][p-1])%mod)%mod;
				if(k==l) f[l][r][k]++;
				
				f[l][r][k]=1ll*f[l][r][k]*tot%mod;
				sf[l][r][k]=(sf[l][r][k-1]+f[l][r][k])%mod;
			}
		}
	}
	cout<<sf[1][n][n];
}

C. 01 Balanced


因为不会差分约束而没有切掉

考虑构造一种前缀和 \(S_i=S_{i-1}+(-1)^{s_i}\)。则题目限制等价于 \(S_{L_i-1}=S_{R,i}\)。

另一方面,限制是 \(|S_i-S_{i-1}|=1,i \in [1,N]\)。

考虑差分约束,将第二个限制视为 \(|S_i-S_{i-1}| \leq 1\),从 \(0\) 开始跑最短路。

由于区间长度为偶数且跑的是最短路,不可能出现 \(S_i=S_{i-1}\) 的情况。

由于边权只有 \(0\) 和 \(1\),可以跑 Dijkstra,甚至 BFS 都行,时间复杂度 \(O(N+M)\)。


#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5,M=2e6+5;
int n,m,s;

struct nod{
	int to,nxt,w;
}e[M*2];
int head[N],cnt;

void add(int u,int v,int w){
	e[++cnt].to=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}

int dis[N];
bool vis[N];

struct abc{
	int num,dis;
};
bool operator <(abc x,abc y){
	return x.dis>y.dis;
}
priority_queue <abc> q;

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		add(i-1+1,i+1,1);
		add(i+1,i-1+1,1);
	}
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		u--;
		add(u+1,v+1,0);
		add(v+1,u+1,0);
	}
	
	memset(dis,0x7f,sizeof(dis));
	dis[1]=0;
	q.push((abc){1,0});
	while(!q.empty()){
		abc tmp=q.top();
		q.pop();
		int u=tmp.num;
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(!vis[v] && dis[u]+e[i].w<dis[v])
				dis[v]=dis[u]+e[i].w,q.push((abc){v,dis[v]});
		}
	}
	for(int i=2;i<=n+1;i++){
		if(dis[i]>dis[i-1]) putchar('0');
		else putchar('1');
	}
}

D. Subset Sum Game


还不是很懂结论和证明……

先考虑转化条件。设 \(S=\sum A_i\),\(sA,sB\) 分别代表两人所取走的数之和。

则 \(sA-sB=sA-(S-sA)=2sA-S\),\(sA=\dfrac{1}{2}(sA-sB+S)\)。

\(L \leq sA \leq R\) 等价于 \(L \leq \dfrac{1}{2}(sA-sB+S) \leq R\),即 \(2L-S \leq sA-sB \leq 2R-S\)。

再继续简化,设 \(X=L+R-S\),则上等价于 \(|(sA-sB)-X| \leq R-L\)。

问题等价于:

有一个变量 \(x\) 初始为 \(L+R-S\),Alice 可以选择一个 \(A_i\) 给 \(x\) 加上,而 Bob 可以选择一个 \(A_i\) 给 \(x\) 减掉。Alice 想最大化 \(|X|\) 而 Bob 相反,求 \(x\) 最后的值。

有结论:

对于所有 \(p\),将 \(p,p+X,A_1,\cdots,A_N\) 从小到大排序为 \(a_1,\cdots,a_{N+2}\),则 \(x\) 最后的值即为 \((a_2-a_1)+(a_4-a_3)+\cdots+(a_{N+2}-a_{N+1})\) 的最小值。

暴力枚举 \(p\) 显然是不行的,但是若 \(p\) 和 \(p+X\) 均落在两个区间中间显然可以左右移动变得更小,所以只需考虑 \(p\) 或 \(p+X\) 为 \(A_t\) 中的某个即可。

时间复杂度 \(O(N^2 \log N)\)。


#include<bits/stdc++.h>
using namespace std;
const long long N=5500,INF=1e18;
long long n,L,R,S,X,anss=INF;
long long a[N];
long long b[N],id;

void solve(long long x){
	id=0;
	if(x<a[1]) b[++id]=x;
	if(x+X<a[1]) b[++id]=x+X;
	for(long long i=1;i<=n;i++){
		b[++id]=a[i];
		if(a[i]<=x && x<a[i+1]) b[++id]=x;
		if(a[i]<=x+X && x+X<a[i+1]) b[++id]=x+X;
	}
	for(int i=1;i<id;i++) if(b[i]>b[i+1]) swap(b[i],b[i+1]);
	long long ans=0;
	for(long long i=1;i<=id;i+=2) ans+=b[i+1]-b[i];
	anss=min(anss,ans);
}

int main(){
	cin>>n>>L>>R;
	for(long long i=1;i<=n;i++) cin>>a[i],S+=a[i];
	sort(a+1,a+n+1);
	a[n+1]=INF;
	X=S-(L+R);
	for(long long i=1;i<=n;i++) solve(a[i]),solve(a[i]-X);
	if(anss<=R-L) puts("Alice");
	else puts("Bob");
}

E. Cheese


赛后直接切了,你管这叫 E(1600)?

发现放奶酪的顺序似乎无关紧要。如果有一前一后两个奶酪,我们可以不管顺序,让后面的奶酪先走到前面的奶酪处,再两块奶酪一起走。这样显然不影响结果(因为奶酪和老鼠间不区分)。

考虑计算最后老鼠 \(X\) 还未吃到奶酪的概率。

参考上面的思路,我们可以先把所有奶酪放好(最后要乘上可重排),再让所有奶酪转到 \(X\) 前面(这个过程中不会有奶酪经过 \(X\)),这时候只要知道还剩下多少奶酪(或老鼠)即可快速算出答案。

先考虑第一步。设 \(f_{i,j,k}\) 表示转移到 \(i+X\) 位置,已经放了 \(j\) 块奶酪,老鼠们已经吃掉放上去的 \(k\) 块奶酪的概率,最后 \(f_{N,N-1,k}\) 即是需要的东西。

转移需要分两步(放奶酪,奶酪走一步让老鼠吃)。

放奶酪(注意可重排):

\[f_{i,j+p,k} \leftarrow f_{i,j+p,k}+\dfrac{1}{p!} A_{i+k}^p f_{i,j,k} \]

吃奶酪:

\[f_{i+1,j,k} \leftarrow f_{i+1,j,k}+\dfrac{1}{2^{j-k}} f_{i,j,k} \]

\[f_{i+1,j,k+1} \leftarrow f_{i+1,j,k+1}+(1-\dfrac{1}{2^{j-k}}) f_{i,j,k} \]

单次时间复杂度 \(O(N^4)\)。

再考虑第二步。假设现在还剩下 \(k-1\) 块奶酪(都在 \(X\) 前面)和 \(k\) 只老鼠,我们要求最后 \(X\) 吃不到奶酪的概率。

打表发现答案是 \(\dfrac{1}{2^{k}-1}\),为什么呢?

将老鼠按顺时针编号,\(X\) 编号为 \(0\)。设 \(p_i\) 表示编号为 \(i\) 的老鼠最后吃不到奶酪的概率。

只考虑老鼠 \(i\) 与老鼠 \(i+1\)(\(i \in [0,k)\)),假设现在轮到 \(i\),还剩下 \(x\) 块奶酪,有 \(4\) 种情况:

所以有 \(p_{i+1}:p_i=(1-\dfrac{1}{2^x})\dfrac{1}{2^{x-1}}:(1-\dfrac{1}{2^{x-1}})\dfrac{1}{2^x}=2:1\),即 \(p_{i+1}=2p_i\)。

自然可得 \(p_i=2^i p_i\)(\(i \in [0,k)\))。

又有 \(\sum p_i=1\),于是 \((2^k-1)p_0=1\),得 \(p_0=\dfrac{1}{2^k-1}\)。得证。

总时间复杂度 \(O(n^5)\),超好写


#include<bits/stdc++.h>
using namespace std;
const long long N=44,mod=998244353,inv2=(mod+1)/2;
long long mul[N],inv[N],pw[N],ipw[N];
long long ksm(long long a,long long x){
	long long tot=1;
	while(x){
		if(x & 1ll) tot=1ll*tot*a%mod;
		a=1ll*a*a%mod;
		x>>=1ll;
	}
	return tot;
}

long long n,A[N],ans;
long long f[2][N][N],id;

int main(){
	cin>>n;
	for(long long i=0;i<n;i++) cin>>A[i],A[i]=1ll*A[i]*ksm(100,mod-2)%mod;
	
	mul[0]=inv[0]=1;
	for(long long i=1;i<=n;i++) mul[i]=mul[i-1]*i%mod;
	inv[n]=ksm(mul[n],mod-2);
	for(long long i=n-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
	
	pw[0]=ipw[0]=1;
	for(long long i=1;i<=n;i++) pw[i]=pw[i-1]*2%mod,ipw[i]=ipw[i-1]*inv2%mod;
	
	
	for(long long X=0;X<n;X++){
		memset(f,0,sizeof(f));
		id=0;
		f[0][0][0]=1;
		for(long long i=1;i<=n;i++){
			id^=1;
			memset(f[id],0,sizeof(f[id]));
			for(long long j=0;j<=n;j++){
				for(long long k=0;k<=n;k++){
					for(long long p=0;j+p<=n;p++)
						f[id][j+p][k]=(f[id][j+p][k]+inv[p]*ksm(A[(X+i)%n],p)%mod*f[id^1][j][k]%mod)%mod;
				}
			}
			
			if(i==n) break;
			id^=1;
			memset(f[id],0,sizeof(f[id]));
			
			for(long long j=0;j<=n;j++){
				for(long long k=0;k<=j;k++){
					f[id][j][k]=(f[id][j][k]+ipw[j-k]*f[id^1][j][k]%mod)%mod;
					f[id][j][k+1]=(f[id][j][k+1]+(1+mod-ipw[j-k])*f[id^1][j][k]%mod)%mod;
				}
			}
		}
		ans=0;
		for(long long i=0;i<=n;i++){
			ans=(ans+mul[n-1]*f[id][n-1][i]%mod*ksm(pw[n-i]-1,mod-2)%mod)%mod;
		}
		cout<<ans<<" ";
	}
}

F. Degree Sequence in DFS Order


这比上场 F 阳间多了

(写完后)阳间个鬼,这官方题解真不是一般的简略,证明根本看不懂啊啊啊啊

Part 1

不妨按 DFS 序标号,即存在 DFS 序之一为 \(1,2,\cdots,N\)。(\(*\))

显然必要条件是:对于所有 \(u \in [2,N]\),存在 \(v<u\) 使 \((u,v)\) 间连边。即若现在已走过 \(1 \sim u\),可以走到 \(u+1\)。(\(**\))

但这并不是充分条件,因为 DFS 序会走到底,可能无法选择 \(u+1\)。

注意到若有 \(a<b<c<d\) 满足 \((a,c)\) 和 \((b,d)\) 连边,可以改为 \((a,d)\) 和 \((b,c)\) 连边,这样度数与性质均不变。

令边 \((u,v)\) 的边权为 \((v-u)^2\),则每次操作必然增大边权和,故存在边权和最大的终态无法继续操作。

假设已走过 \(1 \sim u\),\(u\) 节点不与 \(u+1\) 相连。设 \(u+1\) 与 \(v\) 相连,此时 \(1 < v < u < u+1\) 而 \((1,u)\) 与 \((v,u+1)\) 间连边,矛盾。因此这个终态是满足(\(*\))的。

这样,每一个满足(\(**\))的图都可生成一个满足(\(*\))的图。我们只需要数满足(\(**\))的图的度数序列数量即可。


Part 2

我们来考虑 \(a\) 的限制。

首先显然有 \(\sum\limits_{i=1}^N a_i=2M\)。

更细节的,由于每个节点都向前面连边,可以整体考虑前 \(u\) 个点:

这是必要条件,令人惊讶的是同时也是充分条件。证明如下:

考虑一轮贪心:对于每个点 \(2,3,\cdots,N\),从前面的点选出一个剩余度数最大的点连边。(由于条件 \(1\),可以做到)

如果一轮后所有点剩余度数均不超过 \(M-(N-1)\),就可以贪心匹配。

假设有 \(a_i'>M-(N-1)\),那么由于 \(\sum a_t'=2M-2(N-1)\),不存在另外的 \(a_j' \geq M-(N-1)\)。由条件 \(2\),\(a_i \leq M-(i-2)\)。下面说明在贪心过程中,\(i\) 号点连接了 \(N-i+1\) 条边(也是至多),则有 \(a_i' \leq M-(i-2)-(N-i+1)=M-(N-1)\),矛盾。

首先,在贪心到 \(i\) 时,\(i\) 一定会向前面连一条边。然后,在贪心到 \(k>i\) 时,若存在剩余度数 \(a_{(now)j}>a_{(now)i}\),由于 \(a_i'\) 顶多减到 \(M-(N-1)-1\),\(a_j' \geq M-(N-1)\),这与假设矛盾。因此每一次都可以选择 \(a_i\),\(a_i\) 连的总度数即为 \(N-i+1\)。证毕。


Part 3

现在真的要开始计数了!

先做些变换。令 \(b_0=a_1\),\(b_i=a_{i+1}-1,i \in [1,N)\)(注意所有 \(b_i\) 均为非负整数),则三个条件变为:

这是一个类似卡特兰数的东西,考虑对应到图上。

如果不考虑第三个条件,相当于从原点出发,每次先向上走 \(b_0\),再右移一格,再向上走 \(b_1\),再右移一格……向上走 \(b_{N-1}\)。最后走到 \((N-1,2M-(N-1))\)。限制是不能越过 \(y=x\) 这条线。

用卡特兰数的算法可以轻松算出是 \(\dbinom{2M}{N-1}-\dbinom{2M}{N-2}\)。

考虑减去不满足第三个条件的情况。这里又有一个性质:至多只存在一个 \(K\) 使 \(b_K > M-K\)。证明如下:

设 \(p<q\) 使 \(b_p > M-p\),\(b_q > M-q\)。由条件 \(2\),\(\sum\limits_{i<p} b_i \geq p\),所以 \(\sum\limits_{i=0}^{N-1} b_i \geq \sum\limits_{i<p} b_i+b_p+b_q \geq p+(M-p+1)+(M-q+1)=2M-(q-2) > 2M-(N-1)\),矛盾。

所以,加上第三个条件。我们钦定了 \(b_K \geq M-K+1\),为了维护 \(b_i\) 取非负整数,令 \(b_K \leftarrow b_K-(M-K+1)\),则 \(\sum\limits_{i=0}^{N-1} b_i=2M-(N-1)-(M-K+1)=M-N+K\)。现在的图看起来是这样的:

(图来自官方题解

不妨将图分为三角形与矩形上下两部分来考虑。具体而言,考虑枚举 \(x\),而第一次跃过 \(K\) 的点 \((x,y)\)(即 \(y \geq K\)),分成前后两部分计数。方案数实际等于 \((0,0) \rightarrow (x,K-1)\) 乘上 \((x,K) \rightarrow (N-1,M-N+K)\)(想一想,为什么?)。

于是答案为(\(K=0\) 时与 \(K=1\) 答案相同,但是要特判):

\[Ans=\dbinom{2M}{N-1}-\dbinom{2M}{N-2}-\dbinom{M-1}{M-N}+\sum\limits_{k=1}^{N-1} \sum\limits_{x=0}^{k-1} \Big [\dbinom{x+k-1}{x}-\dbinom{x+k-1}{x-1} \Big] \dbinom{M-x-1}{M-N} \]

交换和号~

\[Ans=\dbinom{2M}{N-1}-\dbinom{2M}{N-2}-\dbinom{M-1}{M-N}+\sum\limits_{x=0}^{N-1} \dbinom{M-x-1}{M-N} \sum\limits_{k=x+1}^{N-1} \dbinom{x+k-1}{x}-\dbinom{x+k-1}{x-1} \]

众所周知 \(\sum\limits_{i=0}^n \dbinom{i}{m}=\dbinom{n+1}{m+1}\),所以拆开得到:

\[Ans=\dbinom{2M}{N-1}-\dbinom{2M}{N-2}-\dbinom{M-1}{M-N}+\sum\limits_{x=0}^{N-1} \dbinom{M-x-1}{M-N} \Big[ \dbinom{x+n-1}{x+1}-\dbinom{2x-1}{x+1}-\dbinom{x+n-1}{x}+\dbinom{2x-1}{x}+[x=0]\Big ] \]

预处理组合数,时间复杂度 \(O(N+M)\)。


这短短的三行代码后是多少艰辛的尝试与推导啊……

#include<bits/stdc++.h>
using namespace std;
const long long N=2e6+5,mod=998244353;
long long mul[N],inv[N];
long long ksm(long long a,long long x){
	long long tot=1;
	while(x){
		if(x & 1ll) tot=1ll*tot*a%mod;
		a=1ll*a*a%mod;
		x>>=1ll;
	}
	return tot;
}
void init(long long lim){
	mul[0]=inv[0]=1;
	for(long long i=1;i<=lim;i++) mul[i]=mul[i-1]*i%mod;
	inv[lim]=ksm(mul[lim],mod-2);
	for(long long i=lim-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
long long C(long long m,long long n){
	if(m<n || m<0 || n<0) return 0;
	return mul[m]*inv[n]%mod*inv[m-n]%mod;
}

long long n,m,ans;
int main(){
	init(N-5);
	cin>>n>>m;
	for(long long x=0;x<n;x++)
		ans=(ans+mod-(C(x+n-1,x+1)+mod-C(2*x-1,x+1)+mod-C(x+n-1,x)+C(2*x-1,x)+mod+(!x))%mod*C(m-x-1,m-n)%mod)%mod;
	cout<<(ans+C(2*m,n-1)+mod-C(2*m,n-2)+mod-C(m-1,m-n))%mod;
}

标签:dbinom,int,题解,奶酪,long,leq,AGC056,sum
来源: https://www.cnblogs.com/Appleblue17/p/15674075.html