AtCoder Grand Contest 043

Preface

很毒的一场,AB自己想的(ORZ陈指导),C完全不会感叹题解的神仙做法,D本来已经做出来了然后想复杂了(或者说是习惯使然?)

E我看了半天题目都看不懂而且这个移动曲线的定义好仙,F神仙题不可做,都弃了得了


A - Range Flip Find Route

垃圾hl666日常看错题目,题目中的翻转矩形看成了翻转正方形

实际上如果没看错就很简单了,因为只能向两个方向走,因此所有连着的黑色格子都可以一次翻转完,搞一个DP记录一下到每个点是否翻转的答案即可

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int n,m,f[N][N][2]; char a[N][N];
int main()
{
	RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%s",a[i]+1);
	memset(f,127,sizeof(f)); if (a[1][1]=='.') f[1][1][0]=0; else f[1][1][1]=1;
	for (i=1;i<=n;++i) for (j=1;j<=m;++j) if (i!=1||j!=1)
	if (a[i][j]=='.') f[i][j][0]=min(min(f[i-1][j][0],f[i-1][j][1]),min(f[i][j-1][0],f[i][j-1][1]));
	else f[i][j][1]=min(min(f[i-1][j][0]+1,f[i-1][j][1]),min(f[i][j-1][0]+1,f[i][j-1][1]));
	return printf("%d",a[n][m]=='.'?f[n][m][0]:f[n][m][1]),0;
}


B - 123 Triangle

稍微要动点脑筋的B题。首先我们容易发现这个数列没做一次每个数都要么减小要么不变

由于刚开始没有\(0\),我们先给它做一次,现在数列里就只有\(0,1,2\)

根据前面说的答案只有这三种,如果你手玩一些数据就会发现\(2\)很少出现,我们先考虑什么时候可能会有\(2\)

经过简单的证明(或者推断?)我们发现答案为\(2\)的必要条件是数列里只有\(0,2\)

原因很简单,因为一旦有\(1\)的出现不管和\(0\)还是\(2\)都会产生\(1\),因此答案不可能为\(2\)

我们先把这种情况放一放,考虑现在数列里有\(1\),那么我们细细一想既然此时答案只能是\(0\)\(1\),那我不是可以把\(2\)都看做\(0\)来做!

所以现在数列里只有\(0,1\)了,然后我们发现此时的减法就变成了异或,然后套路地,我们直接考虑每个数对答案的贡献

是个人都能看出来这个数列的变换过程就是类杨辉三角,因此系数就是组合数

然后现在我们就需要求\(C_n^m\)的奇偶性,这个经典套路,根据卢卡斯定理

\[C_n^m=C_{\lfloor\frac{n}{2}\rfloor}^{\lfloor\frac{n}{2}\rfloor}\times C_{n\mod 2}^{m\mod 2} \]

我们容易发现当\(2\not |C_n^m\)\(m\)二进制下的每一位都要小于\(n\),等价于\(n\operatorname{xor} m=n-m\)

然后我们再回头看只有\(0,2\)的情况,发现直接把\(2\)当成\(1\)来做最后乘上\(2\)即可

#include<cstdio>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int n,b[N],ret; char a[N]; bool one;
int main()
{
	RI i,j; for (scanf("%d%s",&n,a),--n,i=0;i<n;++i) b[i]=abs(a[i]-a[i+1]);
	for (i=0;i<n;++i) if (b[i]==1) one=1;
	if (!one) { for (i=0;i<n;++i) if (b[i]==2) b[i]=1; }
	else for (i=0;i<n;++i) b[i]%=2;
	//for (i=0;i<n;++i) printf("%d",b[i]);
	for (i=0;i<n;++i) if (((n-1)^i)==n-1-i) ret^=b[i];
	return printf("%d",ret*(one?1:2)),0;
}


C - Giant Graph

神仙的一道题,这个思路的真的巧妙啊!

首先我们发现那个点的贡献很奇怪那就从这里入手,我们考虑对于\((x,y,z)\)\((x',y',z')\),若\(x+y+z<x'+y'+z'\),那么选\((x,y,z)\)的贡献不可能超过选\((x',y',z')\)(因为它一个顶\(10^{18}\)个)

因此我们发现我们只需要按照点的\(x+y+z\)的大小倒序贪心地取即可,此时我们可以把无向图变成有向图

然后容易想到我们可以枚举这个值然后考虑有多少点可以选,然后……

然后就发现根本做不出来,直接GG(点开题解)

我们来考虑一个经典的博弈论问题:一张有向图上有一个棋子,两个人轮流移动,每次必须沿着图上的边来走,谁不能走就输了,问哪些点是先手必败的点

容易想到考虑求出每个点的SG函数,然后我们惊奇地发现此时所有\(SG_x=0\)的点都可以选并且满足前面的贪心要求

妙虽然是妙,但这只是一张图啊。别慌,我们观察一下点之间的连边方式,很容易发现此时三张图互相独立,根据博弈论的姿势此时一个点的SG函数就是三张图中对应的点的SG函数的异或值

因此如果一个点\((x,y,z)\)满足\(SG1_x\operatorname{xor}SG2_x\operatorname{xor}SG_3=0\),这个点最后就可以被选择

因此我们对每张图求出SG函数之后然后统计一下\(SG_i=x\)的贡献,然后直接枚举算即可

注意一张DAG的SG函数是\(O(\sqrt n)\)的,因此总复杂度是\(O(n)\)的(如果不算偷懒用的map

#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<map>
#define RI register int
#define CI const int&
#define VI vector <int>:: iterator
using namespace std;
const int N=100005,mod=998244353,base=(long long)1e18%mod;
int n,x,y,ans,pw[N];
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
class Graph
{
	private:
		vector <int> v[N]; int m,sg[N];
		inline int SG(CI x)
		{
			if (~sg[x]) return sg[x]; map <int,bool> ext;
			for (VI to=v[x].begin();to!=v[x].end();++to) ext[SG(*to)]=1;
			while (ext[++sg[x]]); return sg[x];
		}
	public:
		int mx,c[N];
		inline void solve(void)
		{
			RI i; for (scanf("%d",&m),i=1;i<=m;++i)
			if (scanf("%d%d",&x,&y),x<y) v[x].push_back(y); else v[y].push_back(x);
			for (memset(sg,-1,sizeof(sg)),i=1;i<=n;++i) if (!~sg[i]) sg[i]=SG(i);
			for (i=1;i<=n;++i) mx=max(mx,sg[i]),inc(c[sg[i]],pw[i]);
		}
		inline void output(void)
		{
			RI i; printf("%d\n",mx);
			for (i=1;i<=n;++i) printf("%d ",sg[i]); putchar('\n');
			for (i=1;i<=n;++i) printf("%d ",c[i]); putchar('\n');
		}
}G[3];
int main()
{
	RI i,j; for (scanf("%d",&n),pw[0]=i=1;i<=n;++i) pw[i]=1LL*pw[i-1]*base%mod;
	for (i=0;i<3;++i) G[i].solve();
	for (i=0;i<=G[0].mx;++i) for (j=0;j<=G[1].mx;++j)
	inc(ans,1LL*G[0].c[i]*G[1].c[j]%mod*G[2].c[i^j]%mod);
	return printf("%d",ans),0;
}


D - Merge Triplets

ORZ疯狂找性质的陈指导,我混吃混喝就做掉了这道题

首先我们肯定考虑直接对最后的PP序列进行DP,求出方案数

我们容易找到一个性质:\(\forall i\in [1,n-3],a_i<\max(a_{i+1},a_{i+2},a_{i+3})\),很简单,如果一个数字比它后面的三个数都来的大,那么说明它们必然是来自同一组的,这样就有四个数一组不符合题意

然后根据这点我们可以发现,这个序列可以被分成长度为\(1/2/3\)的子串,其中每一个子串的开头都大于后面的数。然后我们进一步发现每个子串的开头的数必然大于它之前的所有数

然后仅仅是这样就完了么?显然不是!因为这样只是保证了每组不会超过三个数,那我们考虑怎么保证总的组数是\(n\)

我们考虑所有长度为\(3\)的字串显然自成三元组,然后就剩下长度为\(1\)和长度为\(2\)

由于长度为\(1\)的字串即可以\(1,1,1\)也可以和一个长度为\(2\)\(1,2\),而长度为\(2\)的就只能和\(1\)拼了

于是我们得出另一个性质:长度为\(2\)的字串个数小于等于长度为\(1\)的字串个数

于是满足了充要性,我们考虑DP,容易想到设\(f_{i,j,k}\)表示已经确定了前\(i\)个数,长度为\(2\)的字串个数减去长度为\(1\)的字串个数,上次的字串开头是\(k\)的方案数

转移就非常显然了,可以直接得出\(O(n^4)\)的暴力,显然容易用前缀和优化到\(O(n^3)\),不过反正都过不了就没有必要

暴力CODE

#include<cstdio>
#define RI int
#define CI const int&
using namespace std;
const int N=105;
int n,mod,f[N*3][N*6][N*3],ans;
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
int main()
{
	RI i,j,k,t; for (scanf("%d%d",&n,&mod),f[0][n*3][0]=1,i=0;i<3*n;++i)
	for (j=0;j<=6*n;++j) for (k=0;k<=3*n;++k) if (f[i][j][k]) for (t=k+1;t<=3*n;++t) 
	{
		inc(f[i+1][j-1][t],f[i][j][k]);
		if (t-1-i>0) inc(f[i+2][j+1][t],1LL*f[i][j][k]*(t-1-i)%mod);
		if (t-1-i>1) inc(f[i+3][j][t],1LL*f[i][j][k]*(t-1-i)%mod*(t-2-i)%mod);
	}
	for (i=0;i<=3*n;++i) inc(ans,f[3*n][i][3*n]); return printf("%d",ans),0;
}

然后被这个枚举开头的思路带偏了,直接掉坑里了

实际上,我们考虑我们只需要确定所有数的相对顺序,然后当相对顺序确定的时候整个序列就被确定了

因此比如在加入长度为\(2\)的字串时由于之前的\(i\)个数有\(i+1\)的相对位置可以放,因此系数就乘上\((i+1)\),其他情况同理

然后就\(O(n^2)\)做完了233

#include<cstdio>
#define RI int
#define CI const int&
using namespace std;
const int N=6005;
int n,mod,f[N][N<<1],ans;
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
int main()
{
	RI i,j,k,t; scanf("%d%d",&n,&mod); n*=3; f[0][n]=1;
	for (i=0;i<n;++i) for (j=-i;j<=i;++j) if (f[i][n+j])
	inc(f[i+1][n+j-1],f[i][n+j]),
	inc(f[i+2][n+j+1],1LL*f[i][n+j]*(i+1)%mod),
	inc(f[i+3][n+j],1LL*f[i][n+j]*(i+2)%mod*(i+1)%mod);
	for (i=-n;i<=0;++i) inc(ans,f[n][n+i]); return printf("%d",ans),0;
}

Postscript

真不是我偷懒,EF看起来就不可做啊,弃了跑路QAQ

posted @ 2020-05-19 21:41  空気力学の詩  阅读(184)  评论(0编辑  收藏  举报