CF1411G No Game No Life 解题报告
给出一个点数为 \(n\) 边数为 \(m\) 的有向无环图,一开始每个点都没有石子,然后进行多次操作,每次在 \([1,n+1]\) 中等概率的选择一个数 \(x\) ,如果 \(x\le n\) 则第 \(x\) 个点上增加一个石子,否则停止操作。
有两个人会在操作完的图上依次进行游戏,每次可以选择一个点将上面的一个石子沿有向边移动到下一个点上,不能操作者输,问先手必胜的概率。
\(n,m\le 10^5\)
这是一道博弈论+概率的题,首先分析在什么情况下先手必胜。
我一开始十分 naive 地套了阶梯博弈的模型到 DAG 上,被别人教育了一番。
由于每次只移动一个石子,所以石子之间没有联系,可以拆成许多的单个石子在 DAG 上移动的公平组合游戏,那么套用 SG 函数就是所有石子的 SG 函数的异或和为 0 时先手必胜。
暴力求 SG 函数是 \(\mathcal{O(n\sqrt{m})}\) 的,因为一个点的 SG 函数的值为 \(x\) 时,就意味着这个点连到其他点的 SG 函数的值存在 \(0\) 到 \(x-1\) ,设 \(g_x\) 为得到一个 SG 函数为 \(x\) 至少需要多少条边,有 \(g_x=\sum\limits_{i=0}^{x-1} g_x+1\) ,这个显然是平方级别的,所以 SG 函数的范围就是 \(0\) 到 \(\sqrt{m}\) ,最终结果的异或和小于 512 。
设 \(f_x\) 为所有石子的 SG 函数异或和为 \(x\) 的概率,每加一个石子整体的异或和就会异或上这个石子的 SG 函数的值,再设 \(cnt_x\) 为 SG 函数的值为 \(x\) 的石子个数,那么有
可以使用高斯消元解方程,复杂度是 \(\mathcal{O(512^3)}\) 一个常数。
你看这不比树上两链的LCP有脑子多了吗。
//No Game No Life 什么时候出第二季啊/kk
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int M=1e5+5,N=525,JYY=998244353;
int read(){
int x=0,y=1;char ch=getchar();
while(ch<'0'||ch>'9') y=(ch=='-')?-1:1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*y;
}
int tot=0,first[M];
struct Edge{ int nxt,to; }e[M<<1];
void add(int x,int y){
e[++tot]=(Edge){first[x],y};
first[x]=tot;
}
int SG[M],cnt[M];bool vis[M],book[M];
void GetSG(int u){
book[u]=1;
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].to;if(book[v]) continue ;
GetSG(v);
}
for(int i=first[u];i;i=e[i].nxt) vis[SG[e[i].to]]=1;
while(vis[SG[u]]) SG[u]++;cnt[SG[u]]++;
for(int i=first[u];i;i=e[i].nxt) vis[SG[e[i].to]]=0;
}
int f[N],eq[N][N];
int qpow(int x,int y){
int res=1;
for(;y;y>>=1,x=x*x%JYY) if(y&1) res=res*x%JYY;
return res;
}
void Gause(){
for(int i=0;i<=511;i++){
for(int j=i+1;j<=511;j++){
if(!eq[j][i]) continue ;
int tmp=eq[j][i]*qpow(eq[i][i],JYY-2)%JYY;
for(int k=i+1;k<=512;k++) eq[j][k]=(eq[j][k]-eq[i][k]*tmp%JYY+JYY)%JYY;
}
}
for(int i=511;i>=0;i--){
for(int j=i+1;j<=511;j++) eq[i][512]=(eq[i][512]-f[j]*eq[i][j]%JYY+JYY)%JYY;
f[i]=eq[i][512]*qpow(eq[i][i],JYY-2)%JYY;
}
}
void solve(){
int n=read(),m=read();
for(int i=1;i<=m;i++){
int x=read(),y=read();
add(x,y);
}
for(int i=1;i<=n;i++) if(!book[i]) GetSG(i);
for(int i=0;i<=512;i++) eq[0][i]=1;
for(int i=1;i<=511;i++){
eq[i][i]=JYY-1;
for(int j=0;j<=511;j++)
eq[i][j]=(eq[i][j]+cnt[i^j]*qpow(n+1,JYY-2)%JYY)%JYY;
eq[i][512]=0;
}
Gause();
printf("%lld\n",(1-f[0]+JYY)%JYY);
}
signed main(){
solve();
}