zoukankan      html  css  js  c++  java
  • 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_{lfloorfrac{n}{2} floor}^{lfloorfrac{n}{2} floor} imes C_{nmod 2}^{mmod 2} ]

    我们容易发现当(2 ot |C_n^m)(m)二进制下的每一位都要小于(n),等价于(noperatorname{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_xoperatorname{xor}SG2_xoperatorname{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
    ",mx);
    			for (i=1;i<=n;++i) printf("%d ",sg[i]); putchar('
    ');
    			for (i=1;i<=n;++i) printf("%d ",c[i]); putchar('
    ');
    		}
    }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 iin [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

  • 相关阅读:
    LeetCode——Generate Parentheses
    LeetCode——Best Time to Buy and Sell Stock IV
    LeetCode——Best Time to Buy and Sell Stock III
    LeetCode——Best Time to Buy and Sell Stock
    LeetCode——Find Minimum in Rotated Sorted Array
    Mahout实现基于用户的协同过滤算法
    使用Java对文件进行解压缩
    LeetCode——Convert Sorted Array to Binary Search Tree
    LeetCode——Missing Number
    LeetCode——Integer to Roman
  • 原文地址:https://www.cnblogs.com/cjjsb/p/12919715.html
Copyright © 2011-2022 走看看