\(\text{Problem}:\)[PKUWC2018] 随机算法
\(\text{Solution}:\)
发现 \(n\) 很小,可以考虑状压 \(dp\)。设 \(f_{S}\) 表示得到集合 \(S\) 最大独立集的概率,\(g_{S}\) 表示集合 \(S\) 最大独立集的大小。
首先预处理 \(g\),枚举 \(S\) 中的元素 \(x\) 并删掉集合 \(S\) 内与 \(x\) 有连边的所有点,得到集合 \(T\),有转移:
\[g_{S}=\max\{g_{T}+1\} \]考虑 \(f\) 的转移,只能从满足 \(g_{T}+1=g_{S}\) 的 \(T\) 转移到 \(S\)。考虑最后一个点的加入是随机的,故有转移:
\[f_{S}=\frac{1}{\lvert S\rvert}\sum\limits_{g_{T}+1=g_{S}}f_{T} \]另一种推导方式是考虑 \(S\rightarrow S\cup T\)(\(T\) 是与加入的 \(i\) 相邻的点集,也包括 \(i\)),此时钦定 \(i\) 被第一个选中,剩下不在 \(S\) 中的元素随意排列的贡献为 \(A_{n-\lvert S\rvert-1}^{T\oplus (T\cap S)-1}\) 。最后除以 \(n!\) 即可。
两种做法的时间复杂度均为 \(O(2^{n}n)\),可以通过。
\(\text{Code}:\)
#include <bits/stdc++.h> #pragma GCC optimize(3) //#define int long long #define ri register #define mk make_pair #define fi first #define se second #define pb push_back #define eb emplace_back #define is insert #define es erase #define vi vector<int> #define vpi vector<pair<int,int>> using namespace std; const int N=20, Mod=998244353; inline int read() { int s=0, w=1; ri char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); } while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch^48), ch=getchar(); return s*w; } int n,m,up,to[N],iiv[N+5],cnt[(1<<N)+5],f[(1<<N)+5],g[(1<<N)+5]; inline int ksc(int x,int p) { int res=1; for(;p;p>>=1, x=1ll*x*x%Mod) if(p&1) res=1ll*res*x%Mod; return res; } signed main() { n=read(), m=read(); iiv[1]=1; for(ri int i=2;i<=N;i++) iiv[i]=1ll*(Mod-Mod/i)*iiv[Mod%i]%Mod; for(ri int i=1;i<=m;i++) { int u,v; u=read(), v=read(); u--, v--; to[u]|=(1<<v), to[v]|=(1<<u); } for(ri int i=0;i<n;i++) to[i]|=(1<<i); up=(1<<n); for(ri int i=1;i<up;i++) { for(ri int j=0;j<n;j++) if((i>>j)&1) g[i]=max(g[i],g[i&(to[j]^(up-1))]+1); } f[0]=1; for(ri int i=1;i<up;i++) cnt[i]=cnt[i>>1]+(i&1); for(ri int i=1;i<up;i++) { for(ri int j=0;j<n;j++) if((i>>j)&1 && g[i]==g[i&(to[j]^(up-1))]+1) f[i]=(f[i]+f[i&(to[j]^(up-1))])%Mod; f[i]=1ll*f[i]*iiv[cnt[i]]%Mod; } printf("%d\n",f[up-1]); return 0; }