把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【AtCoder】AtCoder Grand Contest 017 解题报告

点此进入比赛

A:Biscuits(点此看题面

  • \(n\)个数,问有多少种方法选数,使得选出数之和模\(2\)\(p\)
  • \(n\le50\)

签到题

\(f_{i,0/1}\)表示取到第\(i\)位,当前模\(2\)\(0/1\)的方案数。

显然就考虑取和不取两种转移即可。

代码:\(O(n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50
#define LL long long
using namespace std;
int n,p;LL f[N+5][2];
int main()
{
	RI i,x;for(scanf("%d%d",&n,&p),f[0][0]=i=1;i<=n;++i)
		scanf("%d",&x),x&=1,f[i][0]=f[i-1][0]+f[i-1][x],f[i][1]=f[i-1][1]+f[i-1][x^1];//DP转移
	return printf("%lld\n",f[n][p]),0; 
}

B:Moderate Differences(点此看题面

  • 给定一张\(n\)个各格子的纸条,其中第一个格子填\(A\),最后一个格子填\(B\)
  • 要求相邻格子之间差值的绝对值在\([C,D]\)中。
  • 问是否存在一张合法的纸条。
  • \(n\le5\times10^5\)

简单题

不妨令第一个格子为\(0\),最后一个各自为\(B-A\),显然与原问题等价。

考虑第一个格子可能值为\(0\),第二个格子可能为\([-D,-C]∪[C,D]\)

每新增一个格子,相当于选择一个已有值域区间,加上一个数则左右端点各自加上\(C,D\),减去一个数则左右端点格子减去\(D,C\)

那么我们枚举共加了\(i\)次,对应的最终值域区间为\([i\times C-(n-1-i)\times D,i\times D-(n-1-i)\times C]\)

代码:\(O(n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define LL long long
using namespace std;
int n;LL A,B,C,D; 
int main()
{
	RI i;for(scanf("%d%lld%lld%lld%lld",&n,&A,&B,&C,&D),i=0;i^n;++i)//枚举加了几次
		if(i*C-(n-1-i)*D<=B-A&&B-A<=i*D-(n-1-i)*C) return puts("YES"),0;return puts("NO"),0;//看B-A是否在某一个值域区间中
}

C:Snuke and Spells(点此看题面

  • 有一个长度为\(n\)的数列,你将会进行若干轮操作,若当前数的个数为\(k\),则删去所有\(a_i=k\)\(a_i\)
  • 多组操作,每次修改数列中的一个\(a_i\)。然后回答至少需要修改几个\(a_i\)才能让这个数列能被删空。
  • \(n\le2\times10^5\)

转化

考虑若干相同的数可以转化为一些连续的数,例如连着的\(3\)\(4\)可以变成\(2,3,4\)

假设数字\(i\)出现了\(c_i\)次,就变成了若干\([i-c_i+1,i]\)的区间能否覆盖整个\([1,n]\)

一次修改只会改变两个区间的长度,直接暴力修改。

然后询问至少要该几个数字才能覆盖整个\([1,n]\),就是看有多少位置没被覆盖。

代码:\(O(n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000 
using namespace std;
int n,a[N+5],c[N+5],s[N+5];
int main()
{
	RI Qt,i,x,y,ans=0;for(scanf("%d%d",&n,&Qt),i=1;i<=n;++i) scanf("%d",a+i),++c[a[i]];//统计每个数字出现次数
	for(i=1;i<=n;++i) ++s[max(i-c[i]+1,1)],--s[i+1];for(i=1;i<=n;++i) ans+=!(s[i]+=s[i-1]);//差分实现覆盖
	W(Qt--) scanf("%d%d",&x,&y),c[a[x]]<=a[x]&&!--s[a[x]-c[a[x]]+1]&&++ans,//删去原贡献
		--c[a[x]],++c[a[x]=y],c[y]<=y&&!s[y-c[y]+1]++&&--ans,printf("%d\n",ans);return 0;//加上新贡献
}

D:Game on Tree(点此看题面

  • 给定一棵树,先手和后手轮流操作。
  • 每次断开一条边,并删去不包含\(1\)号点的连通块,不能操作的人就输了。
  • 问谁必胜。
  • \(n\le10^5\)

博弈论

显然这是一道博弈论题目,因此思考如何构造\(SG\)函数。

首先,如果根节点有\(k\)个子节点,我们可以把根节点复制\(k\)份,每个根节点下接一个子节点。

那么这\(k\)个子树就独立了,既然如此原根节点\(SG\)值就是复制得到的\(k\)个根节点\(SG\)值的异或和。

现在就变成了在只有一个儿子时,父节点与子节点\(SG\)值的关系,发现:

  • 若直接断开连接父节点和子节点的边,后继状态\(SG=0\)
  • 若断开其他边,我们发现子节点所有\(SG\)值都有可能转移到。

由于一个状态的\(SG\)等于所有后继状态\(SG\)\(\texttt{mex}\),所以父节点的\(SG\)等于子节点的\(SG+1\)

那么直接\(dfs\)扫一遍就好了。

代码:\(O(n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
I int SG(CI x,CI lst=0) {RI i,t=0;for(i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(t^=SG(e[i].to,x)+1);return t;}//父节点SG=子节点SG+1异或和
int main()
{
	RI i,x,y;for(scanf("%d",&n),i=1;i^n;++i) scanf("%d%d",&x,&y),add(x,y),add(y,x);
	return puts(SG(1)?"Alice":"Bob"),0;//SG>0则先手胜,否则后手胜
}

E:Jigsaw(点此看题面

  • 一块拼图分三部分,中间高度\(m\),左边长度\(a_i\)、离地面\(c_i\),右边长度\(b_i\)、离地面\(d_i\)
  • 给定\(n\)块拼图,你需要把它们排成一排,一块拼图要么放在地上,要么架在另一块拼图上。
  • 要求所有拼图不悬空,问是否存在合法方案。
  • \(n\le10^5,m\le200\)

建图

显然如果一块拼图\(c_i=0\),另一块拼图\(d_i=a_i\),我们就可以把两块拼图拼在一起(\(d_i=0,c_i=b_i\)同理)。

那么那一块拼图看成一条有向边\(c_i=0\)时左端点为\(a_i\),否则为\(m+c_i\)\(d_i=0\)时右端点为\(m+b_i\),否则为\(d_i\)

现在问题就变成了是否能选出若干条从\([1,m]\)的点出发,在\([m+1,2m]\)的点结束的路径,满足所有边都被经过恰好一次。

既然我们只用判YESNO,只要满足三个条件:

  • 对于所有\([1,m]\)的点,入度不大于出度。
  • 对于所有\([m+1,2m]\)的点,出度不大于入度。
  • 不存在环。(即任意连通块存在至少一点入度不等于出度)

代码:\(O(m)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define M 400 
using namespace std;
int n,m,ee,dI[M+5],dO[M+5],f[M+5],fa[M+5];
I int getfa(CI x) {return fa[x]?fa[x]=getfa(fa[x]):x;}//并查集
int main()
{
	#define Union(x,y) (++dO[x],++dI[y],(fx=getfa(x))^(fy=getfa(y))&&(fa[fx]=fy))//并查集上合并
	RI i,a,b,c,d,fx,fy;scanf("%d%d",&n,&m);
	for(i=1;i<=n;++i) scanf("%d%d%d%d",&a,&b,&c,&d),Union(c?m+c:a,d?d:m+b);//拼图转化为边
	for(i=1;i<=2*m;++i) f[getfa(i)]|=dI[i]!=dO[i];//记录一个连通块是否存在入度不等于出度的点
	for(i=1;i<=2*m;++i) if((dI[i]||dO[i])&&!fa[i]&&!f[i]) return puts("NO"),0;//存在环
	for(i=1;i<=m;++i) if(dI[i]>dO[i]) return puts("NO"),0;//[1,m]中有点入度大于出度
	for(i=m+1;i<=2*m;++i) if(dI[i]<dO[i]) return puts("NO"),0;return puts("YES"),0;//[m+1,2m]中有点出度大于入度
}

F:Zigzag(点此看题面

  • 有一个\(n\)行的三角形,第\(i\)行有\(i\)个点。每次只能从\((i,j)\)走到\((i+1,j)\)\((i+1,j+1)\)
  • 你要找出\(m\)条从上往下的路径,满足第\(i\)条路径任意时刻都在第\(i-1\)条路径的右边。
  • 同时你有\(k\)个限制,表示某一次路径在某一层必须走某一个方向。
  • \(n,m\le20,k\le (n-1)\times m\)

状压

考虑我们完全可以把一条路径状压起来。

假设上一次路径为\(pre\),这一次路径为\(now\),那么这次路径是合法的当且仅当\(now\)二进制下任意前缀和始终大于等于\(pre\)对应前缀和。

这也是我仅能想到的东西了。。。

动态规划

我们设\(f_{i,j,k}\)表示当前做到第\(i\)条路径,前\(j-1\)位已经确定完毕且与第\(i-1\)条路径相同,第\(i-1\)条路径为\(k\)

转移时分类讨论:

  • 如果前一条路径这一位为\(1\),当前路径这一位为\(0\),显然非法。
  • 如果前一条路径与当前路径这一位相同,直接转移。
  • 如果前一条路径与当前路径这一位不同,由于已经判掉了\(1,0\)的情况,那么只可能是\(0,1\)
    • 考虑我们找到前一条路径接下来的第一个\(1\),然后把它移到前面来,发现这并不影响之后的决策。
    • 而如果找不到\(1\),即接下来全是\(0\),那么更加可以任填了。

就按照这种思路\(DP\)就好了,其实很好理解。

代码:\(O(nm2^{n-1})\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 20
#define X 1000000007
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,a[N+5][N+5],p[1<<N][N+5],f[N+5][1<<N];
int main()
{
	RI i,j,k,t,x,y,l;scanf("%d%d%d",&n,&m,&t),l=1<<n-1;
	for(memset(a,-1,sizeof(a)),i=1;i<=t;++i) scanf("%d%d",&x,&y),scanf("%d",a[x]+y-1);//存储限制
	for(k=0;k^l;++k) for(j=0;j^n;++j) {for(t=j+1;t^n&&!(k>>t&1);++t);p[k][j]=t^n?t:-1;}//预处理k第j位之后的第一个1
	for(f[n-1][0]=i=1;i<=m;++i)//枚举链
	{
		for(k=0;k^l;++k) f[0][k]=f[n-1][k];for(j=1;j^n;++j) for(k=0;k^l;++k) f[j][k]=0;//初始化
		for(j=0;j^(n-1);++j) for(k=0;k^l;++k) if(f[j][k]) for(x=0;x^2;++x)
		{
			if(~a[i][j]&&a[i][j]^x) continue;if(k>>j&1&&!x) continue;//不满足限制,或填法为1,0直接跳过
			if((k>>j&1)==x) {Inc(f[j+1][k],f[j][k]);continue;}//与前一条链相同直接转移
			~(t=p[k][j])?Inc(f[j+1][k^(1<<j)^(1<<t)],f[j][k]):Inc(f[j+1][k^(1<<j)],f[j][k]);//把下一个1移上来,找不到则随意转移
		}
	}
	for(t=k=0;k^l;++k) Inc(t,f[n-1][k]);return printf("%d\n",t),0;//统计答案
}
posted @ 2020-11-04 18:01  TheLostWeak  阅读(151)  评论(0编辑  收藏  举报