zoukankan      html  css  js  c++  java
  • PKUWC2018 随机算法 和 猎人杀

    随机算法

    我们知道,求任意图的最大独立集是一类NP完全问题,目前还没有准确的多项式算法,但是有许多多项式复杂度的近似算法。

    例如,小 C 常用的一种算法是:

    1. 对于一个 (n) 个点的无向图,先等概率随机一个 (1ldots n) 的排列 (p[1ldots n])

    2. 维护答案集合 (S) ,一开始 (S) 为空集,之后按照 (i=1ldots n) 的顺序,检查 ({p[i]}cup S) 是否是一个独立集,如果是的话就令 (S={p[i]}cup S)

    3. 最后得到一个独立集 (S) 作为答案。

    小 C 现在想知道,对于给定的一张图,这个算法的正确率,输出答案对 (998244353) 取模

    对于 (100\%) 的数据,有(1leq nleq 20,0leq mleq frac{n imes (n-1)}{2}),保证给定的图没有重边和自环。

    题解

    直接做的话设 (dp(s,t)) 表示排列出现了 (s) 中的点,独立集为 (t) 的概率。(O(3^nn)) 显然不行。

    构造:不随机 (1sim n) 的排列,每次独立随机一个点看是否能加入独立集。如果不能则继续随机,直到能加入为止。

    易证这两种方式是等价的。虽然我们只维护了 (t),但 (s) 中的点再次出现时会直接跳过,非 (s) 中但与 (t) 相邻的点对排列顺序与独立集都无影响,所以与 (t) 不相邻的点会等概率出现。即两种方式的所有点第一次出现的顺序等价。

    那么现在不需要知道 (s),即哪些点被选过了。记 (f(t)) 表示独立集为 (t) 的概率,转移就在与 (t) 不相邻的点中等概率随机。

    统计答案:每个点集作为独立集的概率×[大小=最大独立集大小]。

    时间复杂度 (O(2^n n))

    CO int N=20;
    int e[N],f[1<<N];
    
    int main(){
    	int n=read<int>();
    	for(int i=0;i<n;++i) e[i]|=1<<i;
    	for(int m=read<int>();m--;){
    		int u=read<int>()-1,v=read<int>()-1;
    		e[u]|=1<<v,e[v]|=1<<u;
    	}
    	f[0]=1;
    	for(int s=0;s<1<<n;++s)if(f[s]){
    		int cnt=0;
    		for(int i=0;i<n;++i) cnt+=!(e[i]&s);
    		cnt=fpow(cnt,mod-2);
    		for(int i=0;i<n;++i)if(!(e[i]&s))
    			f[s|1<<i]=add(f[s|1<<i],mul(f[s],cnt));
    	}
    	int siz=0;
    	for(int s=0;s<1<<n;++s)if(f[s])
    		siz=max(siz,popcount(s));
    	int ans=0;
    	for(int s=0;s<1<<n;++s)if(popcount(s)==siz)
    		ans=add(ans,f[s]);
    	printf("%d
    ",ans);
    	return 0;
    }
    

    猎人杀

    猎人杀是一款风靡一时的游戏“狼人杀”的民间版本,他的规则是这样的:

    一开始有 (n) 个猎人,第 (i) 个猎人有仇恨度 (w_i) ,每个猎人只有一个固定的技能:死亡后必须开一枪,且被射中的人也会死亡。

    然而向谁开枪也是有讲究的,假设当前还活着的猎人有 ([i_1ldots i_m]),那么有 (frac{w_{i_k}}{sum_{j = 1}^{m} w_{i_j}}) 的概率是向猎人 (i_k) 开枪。

    一开始第一枪由你打响,目标的选择方法和猎人一样(即有 (frac{w_i}{sum_{j=1}^{n}w_j}) 的概率射中第 (i) 个猎人)。由于开枪导致的连锁反应,所有猎人最终都会死亡,现在 (1) 号猎人想知道它是最后一个死的的概率。

    答案对 (998244353) 取模。

    对于 (100\%) 的数据,有 (w_i>0),且 (1leq sumlimits_{i=1}^{n}w_i leq 100000)

    题解

    题目描述中给出的随机方式相当于还是在随机排列,这是不好做的。

    构造:不随机排列,每次独立随机选一个人(无论死活)。如果死了则继续随机,直到活着为止。

    易证这两种方式是等价的。由于随机到死人的时候不管,所以概率分布还是一样的。

    [P(i)=frac{kill}{sum}P(i)+frac{w_i}{sum}\ P(i)=frac{w_i}{sum-kill} ]

    有了这个构造之后我们再来看这道题。现在我们需要 (1) 号猎人死之前所有猎人都死了。这个显然不好做,于是考虑容斥。

    (1) 号死之前 (s) 集合里的人没死,则答案为

    [sum_s(-1)^{|s|}sum_{i=0}^infty(1-P(s)-P(1))^iP(1)\ =sum_s(-1)^{|s|}frac{w_1}{w_s+w_1} ]

    注意到 (sum wleq 10^5),所以可以求出 (cnt_k) 表示所有满足 (w_s=k)(s) 的容斥系数 ((-1)^{|s|}) 的和。

    上生成函数,求 (prod(1-x^w)) 即可。分治NTT解决,时间复杂度 (O(n log^2 n))

    CO int N=2*131072;
    int omg[2][N],rev[N];
    
    void NTT(poly&a,int dir){
    	int lim=a.size(),len=log2(lim);
    	for(int i=0;i<lim;++i) rev[i]=rev[i>>1]>>1|(i&1)<<(len-1);
    	for(int i=0;i<lim;++i)if(i<rev[i]) swap(a[i],a[rev[i]]);
    	for(int i=1;i<lim;i<<=1)
    		for(int j=0;j<lim;j+=i<<1)for(int k=0;k<i;++k){
    			int t=mul(omg[dir][N/(i<<1)*k],a[j+i+k]);
    			a[j+i+k]=add(a[j+k],mod-t),a[j+k]=add(a[j+k],t);
    		}
    	if(dir==1){
    		int ilim=fpow(lim,mod-2);
    		for(int i=0;i<lim;++i) a[i]=mul(a[i],ilim);
    	}
    }
    poly operator*(poly a,poly b){
    	int n=a.size()-1,m=b.size()-1;
    	int lim=1<<(int)ceil(log2(n+m+1));
    	a.resize(lim),NTT(a,0);
    	b.resize(lim),NTT(b,0);
    	for(int i=0;i<lim;++i) a[i]=mul(a[i],b[i]);
    	NTT(a,1),a.resize(n+m+1);
    	return a;
    }
    
    vector<int> a[2*N];
    int tot,h[2*N],top;
    
    IN bool cmp(int i,int j){
    	return a[i].size()>a[j].size();
    }
    int main(){
    	omg[0][0]=1,omg[0][1]=fpow(3,(mod-1)/N);
    	omg[1][0]=1,omg[1][1]=fpow(omg[0][1],mod-2);
    	for(int i=2;i<N;++i){
    		omg[0][i]=mul(omg[0][i-1],omg[0][1]);
    		omg[1][i]=mul(omg[1][i-1],omg[1][1]);
    	}
    	int n=read<int>(),w=read<int>();
    	for(int i=2;i<=n;++i){
    		int w=read<int>();
    		a[++tot].resize(w+1);
    		a[tot][0]=1,a[tot][w]=mod-1;
    		h[++top]=tot;
    	}
    	make_heap(h+1,h+top+1,cmp);
    	while(top>=2){
    		int x=h[1];pop_heap(h+1,h+top+1,cmp),--top;
    		int y=h[1];pop_heap(h+1,h+top+1,cmp),--top;
    		a[++tot]=a[x]*a[y];
    		h[++top]=tot,push_heap(h+1,h+top+1,cmp); // edit 1: cmp
    	}
    	int x=h[1],ans=0;
    	for(int i=0;i<(int)a[x].size();++i)
    		ans=add(ans,mul(mul(w,fpow(i+w,mod-2)),a[x][i]));
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    给msde加装企业管理器
    InterBase 数据库与驱动 版本不同
    delphi 演示数据路径
    TNetHTTPClient 使用
    MYSQL之库操作
    MYSQL之数据操作
    MYSQL之表操作
    MYSQL之视图、触发器、存储过程、函数、事物、数据库锁和数据库备份
    数据库三范式详解
    MYSQL之索引原理与慢查询优化
  • 原文地址:https://www.cnblogs.com/autoint/p/12143026.html
Copyright © 2011-2022 走看看