CF1411G No Game No Life 解题报告

给出一个点数为 (n) 边数为 (m) 的有向无环图,一开始每个点都没有石子,然后进行多次操作,每次在 ([1,n+1]) 中等概率的选择一个数 (x) ,如果 (xle n) 则第 (x) 个点上增加一个石子,否则停止操作。

有两个人会在操作完的图上依次进行游戏,每次可以选择一个点将上面的一个石子沿有向边移动到下一个点上,不能操作者输,问先手必胜的概率。

(n,mle 10^5)

这是一道博弈论+概率的题,首先分析在什么情况下先手必胜。

我一开始十分 naive 地套了阶梯博弈的模型到 DAG 上,被别人教育了一番。

由于每次只移动一个石子,所以石子之间没有联系,可以拆成许多的单个石子在 DAG 上移动的公平组合游戏,那么套用 SG 函数就是所有石子的 SG 函数的异或和为 0 时先手必胜。

暴力求 SG 函数是 (mathcal{O(nsqrt{m})}) 的,因为一个点的 SG 函数的值为 (x) 时,就意味着这个点连到其他点的 SG 函数的值存在 (0)(x-1) ,设 (g_x) 为得到一个 SG 函数为 (x) 至少需要多少条边,有 (g_x=sumlimits_{i=0}^{x-1} g_x+1) ,这个显然是平方级别的,所以 SG 函数的范围就是 (0)(sqrt{m}) ,最终结果的异或和小于 512 。

(f_x) 为所有石子的 SG 函数异或和为 (x) 的概率,每加一个石子整体的异或和就会异或上这个石子的 SG 函数的值,再设 (cnt_x) 为 SG 函数的值为 (x) 的石子个数,那么有

[f_x=frac{1}{n+1}sumlimits_{i=0}^{511} f_i imes cnt_{x^i} ]

[sumlimits_{i=0}^{511} f_i = 1 ]

可以使用高斯消元解方程,复杂度是 (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
",(1-f[0]+JYY)%JYY);
}

signed main(){
	solve();
}
原文地址:https://www.cnblogs.com/wzp-blog/p/14348162.html