• 【AtCoder】AtCoder Grand Contest 017 解题报告


    点此进入比赛

    A:Biscuits(点此看题面

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

    签到题

    (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
    ",f[n][p]),0; 
    }
    

    B:Moderate Differences(点此看题面

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

    简单题

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

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

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

    那么我们枚举共加了(i)次,对应的最终值域区间为([i imes C-(n-1-i) imes D,i imes D-(n-1-i) imes 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)才能让这个数列能被删空。
    • (nle2 imes10^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
    ",ans);return 0;//加上新贡献
    }
    

    D:Game on Tree(点此看题面

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

    博弈论

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

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

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

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

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

    由于一个状态的(SG)等于所有后继状态(SG)( exttt{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)块拼图,你需要把它们排成一排,一块拼图要么放在地上,要么架在另一块拼图上。
    • 要求所有拼图不悬空,问是否存在合法方案。
    • (nle10^5,mle200)

    建图

    显然如果一块拼图(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,mle20,kle (n-1) imes 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
    ",t),0;//统计答案
    }
    
  • 相关阅读:
    懒加载与预加载
    Javascript之 Ajax 与 JSON
    Q&A:为什么prototype中的方法不能赋值给变量,再调用?
    Javascript之 浅克隆 与 深克隆
    Javascript之 __proto__ 与 prototype
    React入门(二)—— Props属性
    React入门(一)—— State属性
    Flask调用layer-iframe后,如何从服务器端关闭弹出窗
    Python从入门到项目实践(明日科技 吉林大学出版社)
    从实例到数理来解析感知机学习算法(PLA)
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC017.html
Copyright © 2020-2023  润新知