\(\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;
}