随机算法
我们知道,求任意图的最大独立集是一类NP完全问题,目前还没有准确的多项式算法,但是有许多多项式复杂度的近似算法。
例如,小 C 常用的一种算法是:
-
对于一个 (n) 个点的无向图,先等概率随机一个 (1ldots n) 的排列 (p[1ldots n])。
-
维护答案集合 (S) ,一开始 (S) 为空集,之后按照 (i=1ldots n) 的顺序,检查 ({p[i]}cup S) 是否是一个独立集,如果是的话就令 (S={p[i]}cup S)。
-
最后得到一个独立集 (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)
题解
题目描述中给出的随机方式相当于还是在随机排列,这是不好做的。
构造:不随机排列,每次独立随机选一个人(无论死活)。如果死了则继续随机,直到活着为止。
易证这两种方式是等价的。由于随机到死人的时候不管,所以概率分布还是一样的。
有了这个构造之后我们再来看这道题。现在我们需要 (1) 号猎人死之前所有猎人都死了。这个显然不好做,于是考虑容斥。
记 (1) 号死之前 (s) 集合里的人没死,则答案为
注意到 (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;
}