AtCoder Grand Contest 005

AtCoder Grand Contest 005

A - STring

翻译

给定一个只包含(ST)的字符串,如果出现了连续的(ST),就把他删去,然后所有位置前移。问最后剩下的串长。

题解

模拟栈,和维护括号一样的。

#include<iostream>
#include<cstring>
using namespace std;
#define MAX 200200
char ch[MAX];
int ans,tot;
int main()
{
	cin>>(ch+1);
	for(int i=1,l=strlen(ch+1);i<=l;++i)
		if(ch[i]=='S')++tot;
		else tot?--tot:++ans;
	cout<<ans+tot<<endl;
	return 0;
}

B - Minimum Sum

翻译

给定排列(a_i)

(sum_{l=1}^nsum_{r=l}^nmin(a_l,a_{l+1},...,a_r))

题解

比较套路的题目,对于每个数维护左右区间第一个比它大的数字,那么当前的这部分区间的最小值都是这个值,直接算贡献即可。

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MAX 200200
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int n,a[MAX],Q[MAX],top;
int l[MAX],r[MAX];
ll ans;
int main()
{
	n=read();
	for(int i=1;i<=n;++i)a[i]=read(),l[i]=1,r[i]=n;
	top=0;
	for(int i=1;i<=n;++i)
	{
		while(top&&a[Q[top]]>a[i])--top;
		if(top)l[i]=Q[top]+1;
		Q[++top]=i;
	}
	top=0;
	for(int i=n;i>=1;--i)
	{
		while(top&&a[Q[top]]>a[i])--top;
		if(top)r[i]=Q[top]-1;
		Q[++top]=i;
	}
	for(int i=1;i<=n;++i)ans+=1ll*(i-l[i]+1)*(r[i]-i+1)*a[i];
	cout<<ans<<endl;
	return 0;
}

C - Tree Restoring

翻译

有一个(n)个节点的树,告诉你距离每个点的最大距离,问这样的树是否存在。

题解

一个性质,距离树上任意一点的最远点一定是直径的一个端点。那么先看看直径是否存在,然后看看剩下的点的距离是否都满足大于直径长度的一半,对于直径长度的奇偶性分开考虑一下。细节好烦啊。

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 111
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int n,a[MAX],mx,vis[MAX];
void Out(){puts("Impossible");exit(0);}
int main()
{
	n=read();
	for(int i=1;i<=n;++i)mx=max(mx,a[i]=read());
	for(int i=1;i<=n;++i)vis[a[i]]++;
	if(mx&1)
	{
		for(int i=mx-mx/2;i<=mx;++i)if((vis[i]-=2)<0)Out();
		for(int i=mx-mx/2;i;--i)if(vis[i])Out();
	}
	else
	{
		if(!vis[mx/2])Out();vis[mx/2]-=1;
		for(int i=mx/2+1;i<=mx;++i)if((vis[i]-=2)<0)Out();
		for(int i=mx/2;i;--i)if(vis[i])Out();
	}
	puts("Possible");
	return 0;
}

D - ~K Perm Counting

翻译

给定(n,K)。求(1..n)所有排列中,不存在(|a_i-i|=K)的排列个数。

题解

看到题目就可以往容斥的方面靠。求解至少有(K)个不合法的方案数,往(dp)方面靠。

发现所有可能出现上述情况的数,一定都是模(K)意义下相等的数,那么把所有这样的数给扣下来,显然就变成了给你一个两侧都是(m)个点的二分图,第(i)个点向着(i-1)(i+1)连边,求连了(x)条边的方案数。这个直接简单(dp)算,状压一下下面的相邻的两个点的状态转移就好饿了。然后再把所有的结果全部合并,显然合并的过程互不影响,就是一个背包。

这样求出来的是恰好匹配的方案数,但是剩下的数可以随便放,所以乘阶乘,从恰好变成了至少,那么容斥计算答案即可。

相当好的一道题目。

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define MOD 924844033
#define MAX 2020
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
int g[MAX][MAX][4];
int f[MAX],jc[MAX];
int n,K,m,ans;
int main()
{
	n=read();K=read();m=(n-1)/K+1;
	jc[0]=1;
	for(int i=1;i<=n;++i)jc[i]=1ll*jc[i-1]*i%MOD;
	g[1][0][0]=1;g[1][1][2]=1;
	for(int i=1;i<m;++i)
		for(int j=0;j<=i;++j)
			for(int k=0;k<4;++k)
			{
				if(!g[i][j][k])continue;
				add(g[i+1][j][k>>1],g[i][j][k]);
				if(!(k&1))add(g[i+1][j+1][k>>1],g[i][j][k]);
				add(g[i+1][j+1][(k>>1)|2],g[i][j][k]);
			}
	f[0]=1;int s=0;
	for(int i=1;i<=K;++i)
	{
		int p=(n-i)/K+1;s+=p;
		for(int j=s;j;--j)
			for(int k=1;k<=p;++k)
				if(j>=k)add(f[j],1ll*f[j-k]*((g[p][k][0]+g[p][k][1])%MOD)%MOD);
	}
	for(int i=0;i<=n;++i)f[i]=1ll*f[i]*jc[n-i]%MOD;
	for(int i=0;i<=n;++i)add(ans,(i&1)?MOD-f[i]:f[i]);
	printf("%d
",ans);
	return 0;
}

E - Sugigma: The Showdown

翻译

有一个(n)个点的图,有(n-1)条红边,(n-1)条蓝边,每种颜色的边都连成了一棵树。(A)控制一个棋子从(X)开始,(B)控制一个棋子从(Y)开始。每次(A)可以不动或者走一条红边,(B)可以不动或者走一条蓝边。如果棋子相遇则游戏结束。(A)希望最大化游戏时间,(B)希望最小化游戏时间,求出游戏进行的时间,如果可以无限循环下去输出(-1)

题解

我只感觉自己有些想法,但是细节什么的有点难考虑啊。(还是题解好)

先考虑如何判断游戏会永远进行下去。只要不出现被抓住的情况就可以进行下去啊。那么什么情况下会被抓住呢?假设当前(A)的位置是(u)(B)的位置是(v)。如果(u)以及使用红边下与其相邻的所有点在蓝边的情况下都与(v)的距离不超过(1),那么就原地等死吧。否则,反过来,如果出现了一条边((u,v)),满足(dis\_blue(u,v)gt 2),并且(A)已经到了(u)位置而且(B)还没抓住,那么只要(B)一靠近,(A)只需要沿着这条边往另外一个方向走,(B)至少要走两步才能过来,那么(A)接着换方向就好了,那么这样就停不下来了。

前面既然用了"否则"这个词,意味着如果不能永远进行下去,那么(A)一定会被抓(这不废话吗)。那么显然不会走到任何一条满足在蓝树上两点距离大于(2)的边,深思熟虑一波,发现如果不存在这样的边的话,(A)打死都走不出(B)的子树。那么以(Y)为根节点,找到所有(A)能够到达的点,显然(A)只会前往一个深度最深的点然后在那里等死。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 200200
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
struct Line{int v,next,col;}e[MAX<<2];
int h[MAX],cnt=1,E[MAX][2];
inline void Add(int u,int v,int col){e[cnt]=(Line){v,h[u],col};h[u]=cnt++;}
int dfn[MAX],tim,fa[MAX],dep[MAX],low[MAX],dis[MAX],X,Y,n;
bool inf[MAX],vis[MAX];
void dfs(int u,int ff)
{
	dfn[u]=++tim;fa[u]=ff;dep[u]=dep[ff]+1;
	for(int i=h[u];i;i=e[i].next)
		if(e[i].col&&e[i].v!=ff)
			dfs(e[i].v,u);
	low[u]=++tim;
}
bool check(int x,int y)
{
	if(dfn[x]>dfn[y])swap(x,y);
	if(low[x]>=low[y])return dep[y]-dep[x]>2;
	if(fa[x]==fa[y])return false;
	return true;
}
void BFS()
{
	queue<int> Q;vis[X]=true;Q.push(X);
	while(!Q.empty())
	{
		int u=Q.front();Q.pop();
		for(int i=h[u];i;i=e[i].next)
			if(!e[i].col&&!vis[e[i].v])
			{
				dis[e[i].v]=dis[u]+1;
				if(dis[e[i].v]<dep[e[i].v])
					vis[e[i].v]=true,Q.push(e[i].v);
			}
	}
}
int main()
{
	n=read();X=read();Y=read();
	for(int i=1;i<n;++i)E[i][0]=read(),E[i][1]=read();
	for(int i=1;i<n;++i)
	{
		int u=read(),v=read();
		Add(u,v,1);Add(v,u,1);
	}
	dep[0]=-1;dfs(Y,0);
	for(int i=1;i<n;++i)
	{
		int u=E[i][0],v=E[i][1];
		if(check(u,v))inf[u]=inf[v]=true;
		else Add(u,v,0),Add(v,u,0);
	}
	BFS();
	for(int i=1;i<=n;++i)if(vis[i]&&inf[i]){puts("-1");return 0;}
	int ans=0;
	for(int i=1;i<=n;++i)if(vis[i])ans=max(ans,dep[i]<<1);
	printf("%d
",ans);
	return 0;
}

F - Many Easy Problems

题面

给定一个(n)个节点的树,定义(f(S))表示在树上包含点集(S)的最小联通块的大小。对于每一个(k),求出所有大小为(k)的点集的(f(S))的和。模数(924844033),原根是(5)

题解

总是觉得自己好(naive)啊,碰到题目总是毫无思路怎么办啊?我还有救吗?

我们对于每一个点分开考虑他在什么时候会对于一个点集产生贡献。显然这样子是不好算的,还是正难则反,我们考虑它在什么时候不会对于一个点集产生贡献,假设点集大小为(K)。那么当且仅当选定的(K)个点都在以当前点为根时的同一子树内。所以当前点对于点集大小为(K)时,产生的贡献是(C_n^k-sum_{vin son} C_{size[v]}^k),其中(size)是子树大小。

那么对于每一个(K),我们把所有点产生的贡献给加起来,也就是(nC_n^k-sum_{i=0}^n num[i]*C_i^k),其中(num[i])表示大小为(i)的子树的个数。发现(num)其实很好求,(dfs)一遍就完事了。前面那部分是定值,最后再考虑。后面的式子直接把组合数给拆开,显然是卷积的形式,(NTT)即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define MOD 924844033
#define ll long long
#define MAX 888888
inline int read()
{
	int x=0;bool t=false;char ch=getchar();
	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
	if(ch=='-')t=true,ch=getchar();
	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
	return t?-x:x;
}
struct Line{int v,next;}e[MAX];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,f[MAX],g[MAX],size[MAX];
int jc[MAX],jv[MAX],inv[MAX];
void dfs(int u,int ff)
{
	size[u]=1;
	for(int i=h[u];i;i=e[i].next)
	{
		int v=e[i].v;if(v==ff)continue;
		dfs(v,u);size[u]+=size[v];
		++f[size[v]];
	}
	++f[n-size[u]];
}
int C(int n,int m){if(m>n)return 0;return 1ll*jc[n]*jv[m]%MOD*jv[n-m]%MOD;}
int N,l,r[MAX],W[MAX];
int fpow(int a,int b)
{
	int s=1;
	while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
	return s;
}
void NTT(int *P,int opt)
{
	for(int i=1;i<N;++i)if(i>r[i])swap(P[i],P[r[i]]);
	for(int i=1;i<N;i<<=1)
	{
		int w=fpow(5,(MOD-1)/(i<<1));W[0]=1;
		for(int k=1;k<i;++k)W[k]=1ll*W[k-1]*w%MOD;
		for(int p=i<<1,j=0;j<N;j+=p)
			for(int k=0;k<i;++k)
			{
				int X=P[j+k],Y=1ll*P[i+j+k]*W[k]%MOD;
				P[j+k]=(X+Y)%MOD;P[i+j+k]=(X+MOD-Y)%MOD;
			}
	}
	if(opt==-1)
	{
		reverse(&P[1],&P[N]);
		for(int i=0,inv=fpow(N,MOD-2);i<N;++i)P[i]=1ll*P[i]*inv%MOD;
	}
}
int main()
{
	n=read();
	for(int i=1;i<n;++i)
	{
		int u=read(),v=read();
		Add(u,v);Add(v,u);
	}
	dfs(1,0);
	jc[0]=jv[0]=inv[0]=inv[1]=1;
	for(int i=1;i<=n;++i)jc[i]=1ll*jc[i-1]*i%MOD;
	for(int i=2;i<=n;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=n;++i)jv[i]=1ll*jv[i-1]*inv[i]%MOD;
	for(N=1;N<n+n;N<<=1)++l;
	for(int i=0;i<N;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
	for(int i=0;i<=n;++i)f[i]=1ll*f[i]*jc[i]%MOD;
	for(int i=0;i<=n;++i)g[i]=jv[n-i];
	NTT(f,1);NTT(g,1);
	for(int i=0;i<N;++i)f[i]=1ll*f[i]*g[i]%MOD;
	NTT(f,-1);
	for(int i=1;i<=n;++i)f[i]=1ll*f[i+n]*jv[i]%MOD;
	for(int i=1;i<=n;++i)f[i]=(1ll*n*C(n,i)%MOD+MOD-f[i])%MOD;
	for(int i=1;i<=n;++i)printf("%d
",f[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/cjyyb/p/9675563.html