随机算法
我们知道,求任意图的最大独立集是一类NP完全问题,目前还没有准确的多项式算法,但是有许多多项式复杂度的近似算法。
例如,小 C 常用的一种算法是:
对于一个 \(n\) 个点的无向图,先等概率随机一个 \(1\ldots n\) 的排列 \(p[1\ldots n]\)。
维护答案集合 \(S\) ,一开始 \(S\) 为空集,之后按照 \(i=1\ldots n\) 的顺序,检查 \(\{p[i]\}\cup S\) 是否是一个独立集,如果是的话就令 \(S=\{p[i]\}\cup S\)。
最后得到一个独立集 \(S\) 作为答案。
小 C 现在想知道,对于给定的一张图,这个算法的正确率,输出答案对 \(998244353\) 取模
对于 \(100\%\) 的数据,有\(1\leq n\leq 20,0\leq m\leq \frac{n\times (n-1)}{2}\),保证给定的图没有重边和自环。
题解
直接做的话设 \(dp(s,t)\) 表示排列出现了 \(s\) 中的点,独立集为 \(t\) 的概率。\(O(3^nn)\) 显然不行。
构造:不随机 \(1\sim 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\n",ans); return 0; }
猎人杀
猎人杀是一款风靡一时的游戏“狼人杀”的民间版本,他的规则是这样的:
一开始有 \(n\) 个猎人,第 \(i\) 个猎人有仇恨度 \(w_i\) ,每个猎人只有一个固定的技能:死亡后必须开一枪,且被射中的人也会死亡。
然而向谁开枪也是有讲究的,假设当前还活着的猎人有 \([i_1\ldots 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\),且 \(1\leq \sum\limits_{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(i))^iP(i)\\ =\sum_s(-1)^{|s|}\frac{w_1}{w_s+w_1} \]
注意到 \(\sum w\leq 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\n",ans); return 0; }
来源:https://www.cnblogs.com/autoint/p/12143026.html