• 【AtCoder】AtCoder Grand Contest 043 解题报告($E,F$或许将成为永远的坑?)


    点此进入比赛

    (A):Range Flip Find Route(点此看题面

    大致题意: 给定一个(n imes m)的黑白矩阵,每次操作你可以把一个矩形内所有格子颜色取反。问至少需要多少次操作才能使得从((1,1))((n,m))存在一条只往下或往右走的全由白色格子组成的路径。

    动态规划

    显然,我们可以发现,这操作等价于我们可以对连续行走的一段路颜色取反(这可以自己画图理解一下)。

    因此我们设(f_{i,j,0/1})表示当前走到((i,j)),是否正在取反。考虑转移方程:

    • 对于白色格子,(f_{i,j,0}=min{f_{i-1,j,0},f_{i,j-1,0},f_{i-1,j,1},f_{i,j-1,1}},f_{i,j,1}=INF)
    • 对于黑色格子,(f_{i,j,0}=INF,f_{i,j,1}=min{f_{i-1,j,0}+1,f_{i,j-1,0}+1,f_{i-1,j,1},f_{i,j-1,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 100
    #define INF 1e9
    using namespace std;
    int n,m,f[N+5][N+5][2];char s[N+5][N+5];
    int main()
    {
    	RI i,j;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%s",s[i]+1);
    	s[1][1]^'#'?(f[1][1][0]=0,f[1][1][1]=INF):(f[1][1][0]=INF,f[1][1][1]=1);//初始化第一格
    	for(i=1;i<=n;++i) f[i][0][0]=f[i][0][1]=INF;//把边界赋为INF
    	for(i=1;i<=m;++i) f[0][i][0]=f[0][i][1]=INF;
    	for(i=1;i<=n;++i) for(j=1;j<=m;++j) if(i^1||j^1)
    		s[i][j]^'#'?(f[i][j][0]=min(min(f[i-1][j][0],f[i][j-1][0]),min(f[i-1][j][1],f[i][j-1][1])),f[i][j][1]=INF)//对于白色格子
    		:(f[i][j][0]=INF,f[i][j][1]=min(min(f[i-1][j][0],f[i][j-1][0])+1,min(f[i-1][j][1],f[i][j-1][1])));//对于黑色格子
    	return printf("%d",min(f[n][m][0],f[n][m][1])),0;//输出答案
    }
    

    (B):123 Triangle(点此看题面

    大致题意: 给定一个由(1,2,3)组成的数组(a_{1sim n}),定义(x_{1,j}=a_j,x_{i,j}=|x_{i-1,j}-x_{i-1,j+1}|(1le jle n-i+1)),求(x_{n,1})

    大致思路

    这道题是闪指导在厕所里被灯闪了一下之后秒掉的题(\%\%\%)

    考虑我们先求出(x_2)这个数组,然后就会发现,(x_2)中不可能存在(3),而最终答案中也不可能存在(3)

    也就是说,接下来只有可能有(0,1,2)

    对于只有(0,2)的情况,此时我们可以把(2)看作(1),转化为(0,1)的情况,最后答案乘(2)即可。

    对于同时有(0,1,2)的情况,此时答案绝对不可能为(2),则我们发现这样一来(0)(2)就是等价的了,可以把(2)看作(0),同样转化为(0,1)的情况。

    于是,我们只要考虑(0,1)的情况就可以了,则此时相减取绝对值实际上就是异或操作。

    我们令原本的(n)(1)(因为我们处理的是(x_2)这个数组,而这个数组长度为(n-1)),然后就会发现第(i)个数((0le i<n))对答案的贡献次数实际上就是(C_{n-1}^{i})

    而既然是异或我们只要考虑其奇偶性,即模(2)意义下的值就可以了。

    注意到由于模数是(2),我们不能直接求出组合数。因此我们用卢卡斯定理,这有一个常见的套路。

    因为:

    [C_n^m=C_{n div 2}^{m div 2} imes C_{n mod 2}^{m mod 2} ]

    所以递归下去,就相当于把(n,m)都转化为了二进制数。

    而若二进制下存在某一位使得(n)这一位上为(0)(m)这一位上为(1),则最终的答案就为(0),否则为(1)

    换言之,也就是二进制下(n)的每一位都要大于(m)的对应位,而用式子表示就是(n xor m=n-m)

    代入到这题就是((n-1) xor i=(n-1)-i)

    然后这道题就做完了。

    代码

    #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 1000000
    using namespace std;
    int n,a[N+5];char s[N+5];
    int main()
    {
    	RI i;for(scanf("%d%s",&n,s),--n,i=0;i^n;++i) a[i]=abs(s[i]-s[i+1]);//将原先n减1,计算出x[2]
    	RI f=1;for(i=0;i^n;++i) if(a[i]==1) {f=0;break;}//f=0表示存在1,f=1表示没有1,注意这题刚好能巧妙利用f的值
    	for(i=0;i^n;++i) a[i]==2&&(a[i]=f);//存在1时把2视作0,没有1时把2视作1
    	RI ans=0;for(i=0;i^n;++i) (((n-1)^i)==(n-1)-i)&&(ans^=a[i]);//根据组合数奇偶性判断这一位上数的贡献
    	return printf("%d",ans*(f+1)),0;//存在1时答案乘1不变,不存在1时答案要乘上2
    }
    

    (C):Giant Graph(点此看题面

    大致题意: 有三张(n)个点的图,分别有(m_1,m_2,m_3)条边。现在构造一张新图,每个点的编号用一个三元组((x,y,z))表示,分别代表三张图中的点,且该点点权为(10^{18(x+y+z)})。若(x)(u)之间有边,则((x,y,z))((u,y,z))之间有边,(y,z)两维同理。求新图的最大独立集的点权和。

    贪心

    这个(10^{18(x+y+z)})摆在这里乍一看很吓人,但实际上却让这道题简单了许多。

    为什么呢?我们发现,若(x'+y'+z'<x+y+z),那么即便选择(n^3)(x'+y'+z')(尽管这当然是不可能的),它们的点权和也比不上选择一个(x+y+z)

    于是就自然而然地发现,我们每次应尽量选择(x+y+z)较大的点。

    而且,由于连边规则要求有两维一样,因此有边相连的两个点(x+y+z)的值不可能相等,即同一级别的点是互不影响的。

    综上所述,这道题其实是道贪心题。

    博弈论

    什么?这道题居然会是博弈论?

    考虑到((n,n,n))显然是必选的,因此我们把它设为博弈的终止态。

    如果我们把这个问题看作有一个棋子,每次可以把它移向值更大的点,不能再移动就输了。那么这就相当于是一个由三堆石子构成的(Nim)问题。也就是说,三张图之间相互独立,我们可以分别讨论。

    由于标定了边的方向,原图变成了一张(DAG),而先手和后手轮流选择就恰好是一个独立集的过程。

    因此,对于一个点((x,y,z))(SG1_x xor SG2_y xor SG3_z=0),那么该点就可以被选择。

    而求答案的时候只要枚举前两张图的(SG)(i)(j),统计下每一张图中(SG=k)(10^{18i})的和(tot_k),那么就可以给答案加上:(tot1_{i} imes tot2_{j} imes tot3_{i xor j})

    代码

    #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 X 998244353
    #define Inc(x,y) ((x+=(y))>=X&&(x-=X))
    using namespace std;
    int n,pw[N+5];
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define D isdigit(c=tc())
    		char c,*A,*B,FI[FS];
    	public:
    		I FastIO() {A=B=FI;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
    }F;
    class Graph//处理一张图的答案
    {
    	private:
    		#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
    		int m,ee,lnk[N+5],SG[N+5],vis[N+5];struct edge {int to,nxt;}e[2*N+5];
    		I void dfs(CI x)//搜索求出SG
    		{
    			RI i;for(i=lnk[x];i;i=e[i].nxt) x<e[i].to&&!~SG[e[i].to]&&(dfs(e[i].to),0);//处理后继状态的SG值
    			for(i=lnk[x];i;i=e[i].nxt) x<e[i].to&&(vis[SG[e[i].to]]=x);//标记哪些SG值出现过
    			for(SG[x]=0;vis[SG[x]]==x;++SG[x]);//求出mex
    		}
    	public:
    		int Mx,tot[N+5];
    		I void Init()
    		{
    			RI i,x,y;for(F.read(m),i=1;i<=m;++i) F.read(x),F.read(y),add(x,y),add(y,x);//连边
    			for(i=1;i<=n;++i) SG[i]=-1;for(i=1;i<=n;++i)//初始化SG值为-1表示未求解过
    				!~SG[i]&&(dfs(i),0),Mx<SG[i]&&(Mx=SG[i]),Inc(tot[SG[i]],pw[i]);//统计信息
    		}
    }G1,G2,G3;
    int main()
    {
    	RI i,j,ans=0;F.read(n);
    	for(pw[0]=1,pw[1]=(long long)1e18%X,i=2;i<=n;++i) pw[i]=1LL*pw[i-1]*pw[1]%X;//预处理幂
    	for(G1.Init(),G2.Init(),G3.Init(),i=0;i<=G1.Mx;++i)
    		for(j=0;j<=G2.Mx;++j) ans=(1LL*G1.tot[i]*G2.tot[j]%X*G3.tot[i^j]+ans)%X;//统计答案
    	return printf("%d",ans),0;
    }
    

    (D):Merge Triplets(点此看题面

    大致题意:(3n)个数,你需要把它们分成(n)个有序三元组。每次选出每组第一个数中最小的那一个,取出并放入生成序列。求最终生成序列可能的情况数。

    结论一

    我们发现,由于每次取出最小的数,所以若一个数之后有若干比它小的数(注意是比它小,而不是递减),那么它们都应该属于一个三元组中。

    也就是说,一个数之后最多只能有两个数比它小,且它们必然属于一个集合(这里的集合有别于题目中的三元组,大小可以为(1/2/3))。

    再考虑下一个比它大的数必然是另一集合的开头的数,进而我们发现一个推论:每一个集合开头的数必然大于先前出现过的所有数

    结论二

    结论一实际上只能使得每个集合元素个数小于等于(3),却不能满足这些集合恰好能拼成(n)个三元组。

    考虑对于大小为(3)的集合,它必然是一个三元组;大小为(1)的集合,既可以由大小为(1,1,1)的三个集合拼成一个三元组,也可以由大小为(1,2)的两个集合拼成一个三元组。

    而对于大小为(2)的集合,只能与大小为(1)的集合共同拼成一个三元组。所以我们就得到了结论二:大小为(2)的集合个数小于等于大小为(1)的集合个数

    显然有了这样两个结论,就已经保证了充要性,那么我们就可以(DP)生成序列的方案数了。

    关于转移系数

    我们设(f_{i,j})表示已经确定了前(i)个数,大小为(1)的集合个数减去大小为(2)的集合个数为(j)(j)可能为负,因此我们给它加上(3n)的方案数。

    然后发现这道题似乎用刷表法写会更容易。

    一开始(naive)地去枚举新集合开头的数,也因此要记录下当前集合开头的数,复杂度是(O(n^4))的。(可以用前缀和优化到(O(n^3)),但反正都过不去,懒得写了。。。)

    于是被这个思路带偏,然后就走到死胡同里去了。。。

    然而实际上,我们考虑只要维护出所有数的相对顺序,而确定(n)个数的相对顺序自然就确定了整个序列,也就不需要去枚举具体填什么值了。

    假设当前确定了前(i)个数,然后加入新的一个集合,有三种情况:

    • 新加入的集合大小为(1):根据结论一的推论,这个数必然大于先前所有数,相对大小唯一,转移系数也就是(1)
    • 新加入的集合大小为(2):第(i+2)个数,肯定小于第(i+1)个数,而与已确定的(i)个数相比有(i+1)种相对大小关系((i)个数有(i+1)个空隙),转移系数为(i+1)
    • 新加入的集合大小为(3):第(i+3)个数,与已确定的(i)个数和刚加入的第(i+2)个数相比有(i+2)种相对大小关系,转移系数在(i+1)的基础上再乘一个(i+2)

    具体实现详见代码。

    代码

    #pragma GCC optimize(2)
    #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 6000
    #define Inc(x,y) ((x+=(y))>=X&&(x-=X))
    using namespace std;
    int n,X,f[N+5][2*N+5];
    int main()
    {
    	RI i,j,k,x;scanf("%d%d",&n,&X),n*=3,f[0][n]=1;//这里先将n乘3,方便后续操作
    	for(i=0;i^n;++i) for(j=0;j<=2*n;++j)
    		i+1<=n&&Inc(f[i+1][j+1],f[i][j]),//加入一个数
    		i+2<=n&&(f[i+2][j-1]=(1LL*(i+1)*f[i][j]+f[i+2][j-1])%X),//加入两个数
    		i+3<=n&&(f[i+3][j]=((1LL*(i+1)*(i+2))%X*f[i][j]+f[i+3][j])%X);//加入三个数
    	RI ans=0;for(j=n;j<=2*n;++j) Inc(ans,f[n][j]);return printf("%d",ans),0;//统计并输出答案
    }
    
  • 相关阅读:
    nexus-3.2.0-01.zip安装以及如何启动服务
    Windows环境使用Nexus-3.x搭建Maven私服
    Maven中settings.xml的配置项说明
    Maven之setting.xml配置文件详解
    设置MAVEN_OPTS环境变量
    使用Nexus搭建Maven私服
    spark-submit参数配置
    spark遇到的问题(持续更新)
    hikari参数配置
    java 开发遇到的问题
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC043.html
Copyright © 2020-2023  润新知