zoukankan      html  css  js  c++  java
  • 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();
    }
    
  • 相关阅读:
    AspNetPager.dll 分页控件使用方法、含有代码示例 [转]
    XmlDocument序列化到Session[转]
    静态构造函数
    错误日志[常用方法]
    Vss 源代码管理中的目录问题
    StopWatch 获得更精确的运行时间
    最近写的一个验证码.
    windowsservice 中的 currentdirectory
    vs2005常用快捷键(转贴)
    一个mapyEasy 图片切割工具 及源码
  • 原文地址:https://www.cnblogs.com/wzp-blog/p/14348162.html
Copyright © 2011-2022 走看看