Atcoder AGC005 题解

A - STring

用类似括号匹配的方法搞一下即可…

#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int N=200005;
char s[N];int len,js=0,ans;
int main()
{
	scanf("%s",s+1);
	len=strlen(s+1);
	for(RI i=1;i<=len;++i)
		if(s[i]=='S') ++js;
		else {
			if(js) --js;
			else ++ans;
		}
	printf("%d\n",ans+js);
	return 0;
}

B - Minimum Sum

额,用单调栈找到每个元素左右第一个比它小的元素,就能知道左端点在哪个区间取右端点在哪个区间取,这个元素会是最小值,然后处理每个元素的贡献即可。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
typedef long long LL;
const int N=200005;
int n,top,L[N],R[N],a[N],st[N];LL ans;
int main()
{
	n=read();
	for(RI i=1;i<=n;++i) a[i]=read();
	for(RI i=1;i<=n;++i) {
		while(top&&a[st[top]]>a[i]) --top;
		L[i]=(top?st[top]+1:1),st[++top]=i;
	}
	top=0;
	for(RI i=n;i>=1;--i) {
		while(top&&a[st[top]]>a[i]) --top;
		R[i]=(top?st[top]-1:n),st[++top]=i;
	}
	for(RI i=1;i<=n;++i) ans+=1LL*(i-L[i]+1)*(R[i]-i+1)*a[i];
	printf("%lld\n",ans);
	return 0;
}

C - Tree Restoring

我们知道,树上一个点的最远点,一定是直径的两个端点之一。所以首先我们找出直径的长度LL

如果LL是偶数,那么ai=L2a_i=\frac{L}{2}的点只能有一个,L2&lt;aiL\frac{L}{2} &lt;a_i \leq L的点至少要有两个,如果有更多,则可以通过连接直径上aj=ai1a_j=a_i-1的点jj来达成目标。而ai&lt;L2a_i&lt; \frac{L}{2}的点不能存在。

如果LL是奇数,那么ai=L2a_i=\lceil \frac{L}{2} \rceil的点只能有两个,L2&lt;aiL\lceil \frac{L}{2} \rceil &lt;a_i \leq L至少有两个,ai&lt;L2a_i&lt;\lceil \frac{L}{2} \rceil不能存在。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int N=105;
int a[N],b[N],n,mx;
int main()
{
	scanf("%d",&n);
	for(RI i=1;i<=n;++i) scanf("%d",&a[i]),mx=max(mx,a[i]),++b[a[i]];
	if(mx&1) {
		for(RI i=mx;i>=(mx+1)/2;--i)
			if(b[i]<2) {puts("Impossible");return 0;}
		for(RI i=1;i<(mx+1)/2;++i)
			if(b[i]) {puts("Impossible");return 0;}
		if(b[(mx+1)/2]>2) {puts("Impossible");return 0;}
	}
	else {
		for(RI i=mx;i>mx/2;--i)
			if(b[i]<2) {puts("Impossible");return 0;}
		for(RI i=1;i<mx/2;++i)
			if(b[i]) {puts("Impossible");return 0;}
		if(b[mx/2]!=1) {puts("Impossible");return 0;}
	}
	puts("Possible");
	return 0;
}

D - ~K Perm Counting

考虑容斥,令gig_i表示钦定ii个点不合法,其他点随便的方案数。

答案就是i=0n(1)i+1gi(ni)!\sum_{i=0}^n (-1)^{i+1}g_i(n-i)!

那么怎么求gig_i呢?

假设有一张二分图,左边是每一个值,右边是每一个位子,每一个不合法的匹配,我们连一条边。我们发现这张图长得十分优美:

Atcoder AGC005 题解

是若干条链。我们把所有的链都拉直了进行DP,设f(i,j,0/1)f(i,j,0/1)表示前ii个点里选了jj个不合法匹配,且第ii个点和第i+1i+1个点之间的匹配边是否选择的方案数,那么有:

f(i+1,j,0)=f(i,j,0)+f(i,j,1),f(i+1,j+1,1)=f(i,j,0)f(i+1,j,0)=f(i,j,0)+f(i,j,1),f(i+1,j+1,1)=f(i,j,0)

当然,如果一个点是某一条链的结尾,那么就要强制跳f(i+1,j+1,1)=f(i,j,0)f(i+1,j+1,1)=f(i,j,0)这个转移。

然后令g(i)=f(n,i,0)+f(n,i,1)g(i)=f(n,i,0)+f(n,i,1)即可算出答案。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int mod=924844033,N=4005;
int vis[N][2],ed[N],f[N][N][2],fac[N],js,n,K,ans;
int qm(int x) {return x>=mod?x-mod:x;}
int main()
{
	scanf("%d%d",&n,&K);
	for(RI i=1;i<=n;++i)
		for(RI j=0;j<=1;++j) {
			if(vis[i][j]) continue;
			for(RI x=i,y=j;x<=n;x+=K,y^=1) ++js,vis[x][y]=1;
			ed[js]=1;
		}
	f[1][0][0]=1;
	for(RI i=1;i<=js;++i)
		for(RI j=0;j<=n;++j) {
			f[i+1][j][0]=qm(f[i][j][0]+f[i][j][1]);
			if(!ed[i]) f[i+1][j+1][1]=f[i][j][0];
		}
	fac[0]=1;for(RI i=1;i<=n;++i) fac[i]=1LL*fac[i-1]*i%mod;
	for(RI i=0,fh=1;i<=n;++i,fh=-fh)
		ans=qm(ans+qm(1LL*fh*qm(f[js][i][0]+f[js][i][1])%mod*fac[n-i]%mod+mod));
	printf("%d\n",ans);
	return 0;
}

E - Sugigma: The Showdown

若有红树上有一条边,它的两个端点在蓝树上的距离大于2,则如果先手到达了这两个端点,就必胜。

建出蓝树,以后手起点为根,算出所有点深度。然后建出红树,删掉满足以上条件的边(也就是建的时候就不加)

然后看先手能到那些点。

由于红树上任意一条边连接的两点在蓝树上的距离小于等于2,所以如果一个点到先手起点的距离大于等于到后手起点的距离,先手走这点就一定会被后手抓,所以不能走。这样如果它能到必胜点,就输出-1,否则选择在蓝树上深度最大的那个点,待在那里坐以待毙。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=200005;
int n,s1,s2,tim,ans;
int win[N],dep[N],in[N],out[N],fa[N],vis[N],dis[N],que[N];
struct edge{int x,y;}e1[N];
struct tree{
	int h[N],ne[N<<1],to[N<<1],tot;
	void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
}T1,T2;
void dfs(int x,int las) {
	dep[x]=dep[las]+1,in[x]=++tim,fa[x]=las;
	for(RI i=T2.h[x];i;i=T2.ne[i])
		if(T2.to[i]!=las) dfs(T2.to[i],x);
	out[x]=tim;
}
int check(int x,int y) {
	if(in[x]>in[y]) swap(x,y);
	if(in[x]<=in[y]&&out[x]>=in[y]) return dep[y]-dep[x]>2;
	if(fa[x]==fa[y]) return 0;
	return 1;
}
void bfs() {
	int he=1,ta=1;
	vis[s1]=1,dis[s1]=0,que[1]=s1;
	while(he<=ta) {
		int x=que[he];++he;
		for(RI i=T1.h[x];i;i=T1.ne[i])
			if(dis[x]+1<dep[T1.to[i]]&&!vis[T1.to[i]])
				vis[T1.to[i]]=1,dis[T1.to[i]]=dis[x]+1,que[++ta]=T1.to[i];
	}
}
int main()
{
	int x,y;
	n=read(),s1=read(),s2=read();
	if(s1==s2) {puts("0");return 0;}
	for(RI i=1;i<n;++i) e1[i].x=read(),e1[i].y=read();
	for(RI i=1;i<n;++i) x=read(),y=read(),T2.add(x,y),T2.add(y,x);
	dep[0]=-1,dfs(s2,0);
	for(RI i=1;i<n;++i) {
		if(check(e1[i].x,e1[i].y)) win[e1[i].x]=win[e1[i].y]=1;
		else T1.add(e1[i].x,e1[i].y),T1.add(e1[i].y,e1[i].x);
	}
	bfs();
	for(RI i=1;i<=n;++i)
		if(win[i]&&vis[i]) {puts("-1");return 0;}
		else if(vis[i]) ans=max(ans,dep[i]+dep[i]);
	printf("%d\n",ans);
	return 0;
}

F - Many Easy Problems

容斥。

考虑每一个点对答案的贡献,发现如果一个选点集合的f中,x这个点没有参与贡献,则所有集合中的点都在删掉x的那些连通块中,也就是说,x对k意义下的答案的贡献为:
CnkCaikC_n^k-\sum C_{a_i}^k

其中aia_i为删掉点xx的那些连通块的大小。

我们发现对于任意一个kk,一个指定的CikC_i^k的系数是不变的,预处理这个系数,设为pip_i

则:

ansk=i=knCikpi=1k!i=knpii!(ik)!ans_k=\sum_{i=k}^n C_i^kp_i=\frac{1}{k!}\sum_{i=k}^n \frac{p_ii!}{(i-k)!}

我们发现,构造一个ai=pii!a_i=p_ii!和一个bni=1i!b_{n-i}=\frac{1}{i!},则aabb的卷积的第i+ki+k项就是kk意义下的答案。

题目给出的模数的原根是5,可以NTT。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=200005,LIM=524288,mod=924844033,G=5;
int n,tot;
int h[N],ne[N<<1],to[N<<1],sz[N],p[LIM],tmp[LIM],rev[LIM],fac[N],ni[N];
int qm(int x) {return x>=mod?x-mod:x;}
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
void pre_dfs(int x,int las) {
	sz[x]=1;p[n]=qm(p[n]+1);
	for(RI i=h[x];i;i=ne[i]) {
		if(to[i]==las) continue;
		pre_dfs(to[i],x),sz[x]+=sz[to[i]];
		p[sz[to[i]]]=qm(p[sz[to[i]]]-1+mod);
	}
	if(n-sz[x]) p[n-sz[x]]=qm(p[n-sz[x]]-1+mod);
}
int ksm(int x,int y) {
	int re=1;
	for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
	return re;
}
void NTT(int *a,int n,int x) {
	for(RI i=0;i<n;++i) if(rev[i]>i) swap(a[i],a[rev[i]]);
	for(RI i=1;i<n;i<<=1) {
		int gn=ksm(G,(mod-1)/(i<<1));
		for(RI j=0;j<n;j+=(i<<1)) {
			int t1,t2,g=1;
			for(RI k=0;k<i;++k,g=1LL*g*gn%mod) {
				t1=a[j+k],t2=1LL*g*a[j+i+k]%mod;
				a[j+k]=qm(t1+t2),a[j+i+k]=qm(t1-t2+mod);
			}
		}
	}
	if(x==1) return;
	int inv=ksm(n,mod-2);reverse(a+1,a+n);
	for(RI i=0;i<n;++i) a[i]=1LL*a[i]*inv%mod;
}
int main()
{
	int x,y,kn=1,len=0;
	n=read();
	for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
	pre_dfs(1,0);fac[0]=1;
	for(RI i=1;i<=n;++i)
		fac[i]=1LL*fac[i-1]*i%mod,p[i]=1LL*p[i]*fac[i]%mod;
	ni[n]=ksm(fac[n],mod-2);
	for(RI i=n-1;i>=0;--i) ni[i]=1LL*(i+1)*ni[i+1]%mod;
	for(RI i=0;i<=n;++i) tmp[n-i]=ni[i];
	while(kn<=n+n) kn<<=1,++len;
	for(RI i=1;i<kn;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
	NTT(p,kn,1),NTT(tmp,kn,1);
	for(RI i=0;i<kn;++i) tmp[i]=1LL*tmp[i]*p[i]%mod;
	NTT(tmp,kn,-1);
	for(RI i=1;i<=n;++i) printf("%lld\n",1LL*tmp[n+i]*ni[i]%mod);
	return 0;
}