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

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

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

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

#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

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

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

那么怎么求 g i g_i gi呢?

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

灵魂画手litble

是若干条链。我们把所有的链都拉直了进行DP,设 f ( i , j , 0 / 1 ) f(i,j,0/1) f(i,j,0/1)表示前 i i i个点里选了 j j j个不合法匹配,且第 i i i个点和第 i + 1 i+1 i+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,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) 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) 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意义下的答案的贡献为:
C n k − ∑ C a i k C_n^k-\sum C_{a_i}^k CnkCaik

其中 a i a_i ai为删掉点 x x x的那些连通块的大小。

我们发现对于任意一个 k k k,一个指定的 C i k C_i^k Cik的系数是不变的,预处理这个系数,设为 p i p_i pi

则:

a n s k = ∑ i = k n C i k p i = 1 k ! ∑ i = k n p i i ! ( i − k ) ! ans_k=\sum_{i=k}^n C_i^kp_i=\frac{1}{k!}\sum_{i=k}^n \frac{p_ii!}{(i-k)!} ansk=i=knCikpi=k!1i=kn(ik)!pii!

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

题目给出的模数的原根是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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值