AGC022F Checkers 故事2 题解
将点的坐标用复数表示,设六次单位根
现在我们要讨论一下哪些系数组合可能存在。把操作视为二元运算,根据操作过程建出二叉表达式树,叶子节点表示一开始的
定义二叉森林里一个节点的“位置”为复数
-
每一个非叶子都会在其左边和右边各有恰好一个对应的儿子
-
除了
n-m 个在1 位置的根节点的每一个节点都会存在一个在左边或是右边的父亲
我们可以列出一个
我们要求一组
如果
满足上面三条的
-
由于森林里的每一棵树在
1 位置都有至少一个点,所以r_0+l_0\ge n-m -
至少有一棵树的根节点不是叶子,所以
r_0\ge 1
现在的这些限制,就足以让
不过现在问题来了,满足
问题可以被转化为三角形网格上的随机游走问题,
——其实这个结论还是很理所当然的,你想要是初始所有点位置一样也就是说
现在我们只要解决停留位置的问题,然后减掉一个组合数就可以。我们先考虑位置的实数部分的变化,枚举有多少步是平行于实数轴走的,那么剩下的步数的虚部的安排方案就一共有
我们可以写出答案的表达式:
设
则
这是验题人代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#define LL int
using namespace std;
const int N=2.1e6+11,p=998244353;
LL frc[N],inv[N],r[N],q[N],f1[N],a1[N],c[N],b[N],k1;
vector<LL>g[N];
inline LL add(LL x,LL y){return x+y>=p?x+y-p:x+y;}
inline LL cmb(LL x,LL y){return x<y||y<0?0:1ll*frc[x]*inv[y]%p*inv[x-y]%p;}
inline LL rd(){
register LL i=0,j=1;char g=getchar();
while(g>57||g<48){if(g=='-')j=-1;g=getchar();}
while(g>47&&g<58)i=(i<<3)+(i<<1)+g-48,g=getchar();
return i*j;}
inline LL ksm(LL x,LL y){
register LL k=1,l=x;
while(y){if(y&1)k=k*1ll*l%p;l=l*1ll*l%p,y>>=1;}
return k;
}
inline void bter(){
for(LL i=0;i<k1;i++)r[i]=(r[i>>1]>>1)|(i&1?k1>>1:0);}
inline void ntt(LL *f,bool m){
register LL i,j,k,l,h;
for(i=0;i<k1;i++)
if(i<r[i])swap(f[i],f[r[i]]);
for(i=1;i<k1;i<<=1){
l=ksm(m?3:332748118,(p-1)/(i<<1));
for(j=q[0]=1;j<i;j++)q[j]=q[j-1]*1ll*l%p;
for(j=0;j<k1;j+=i<<1)
for(h=j;h<j+i;h++)
k=q[h-j]*1ll*f[h+i]%p,f[h+i]=add(f[h],p-k),f[h]=add(f[h],k);}
if(!m)for(LL i=0,k=ksm(k1,p-2);i<k1;i++)f[i]=1ll*f[i]*k%p;
}
inline void dntt(LL x,LL y,LL z){
if(y==z){g[x].push_back(a1[y]);return;}
register LL i,s1,s2,k,j,mid=y+z>>1;
dntt(x<<1,y,mid);dntt((x<<1)+1,mid+1,z);
for(k1=1;k1<z-y+1<<1;k1<<=1);bter();
for(i=0;i<k1;i++)c[i]=b[i]=0;
for(i=0,s1=g[(x<<1)+1].size();i<s1;i++)c[i]=g[(x<<1)+1][i];
for(i=mid-y+1<<1,j=1;i>=0;i-=2,j=add(p-j,p-j))b[i]=1ll*cmb(mid-y+1,i>>1)*j%p;
ntt(b,1);ntt(c,1);
for(i=0;i<k1;i++)b[i]=1ll*b[i]*c[i]%p;ntt(b,0);
for(i=0,s2=g[x<<1].size();i<s2;i++)j=z-y+i-(s2>>1),b[j]=add(b[j],g[x<<1][i]);
for(i=0;i<=z-y<<1;i++)g[x].push_back(b[i]);
}
int main()
{
register LL i,j,k,ans=0,n=rd(),m=rd();
for(i=frc[0]=1;i<=n<<1;i++)frc[i]=1ll*frc[i-1]*i%p;
for(--i,inv[i]=ksm(frc[i],p-2);i;i--)inv[i-1]=1ll*inv[i]*i%p;
for(i=(n&1);i<=n;i+=2)a1[i]=1ll*frc[n]*inv[i]%p*inv[n-i>>1]%p*inv[n-i>>1]%p;
dntt(1,0,n);
for(i=0;i<=n<<1;i+=2)ans=add(ans,1ll*g[1][i]*cmb(i,n-m+(i>>1))%p);
if(!(m&1))ans=add(ans,p-cmb(n,m/2));
printf("%d\n",ans);
return 0;
}
特殊奖励 1:你可能遇到这题会想去回顾原版 Checkers,故此奖励的获得条件为:于比赛期间提交 AGC022F 的 AC 代码。