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)的奇偶性,这个经典套路,根据卢卡斯定理:
我们容易发现当(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