【PKUWC2018】隨機算法
Description
我們知道,求任意圖的最大獨立集是一類NP完全問題,目前還沒有準確的多項式算法,但是有許多多項式復雜度的近似算法。
例如,小 C 常用的一種算法是:
1.對於一個 \(n\) 個點的無向圖,先等概率隨機一個 \(1\ldots n\) 的排列 \(p[1\ldots n]\)。
2.維護答案集合 \(S\) ,一開始 \(S\) 為空集,之後按照 \(i=1\ldots n\)的順序,檢查 \(\{p[i]\}\cup S\) 是否是一個獨立集,如果是的話就令 \(S=\{p[i]\}\cup S\)。
3.最後得到一個獨立集 \(S\) 作為答案。
小 C 現在想知道,對於給定的一張圖,這個算法的正確率,輸出答案對 \(998244353\)
Input
第一行兩個非負整數 \(n,m\) 表示給定的圖的點數和邊數。
接下來 \(m\) 行,每行有兩個正整數 \((u,v) (u\neq v)\)描述這張圖的一條無向邊。
Output
輸出正確率,答案對 \(998244353\) 取模。
Solution
看到\(n \leq 20\) 時,有經驗的選手應該就會往狀壓DP上去想.
令\(F_{i,j}\)表示當前獨立集大小為i,不能選的點集為j的方案數,\(S_u\)表示選了\(i\)後不能選的點的集合,易得
\[ F_{i,j\bigcup S_u}=F_{i,j\bigcup S_u}+F_{i-1,j}*A_{n-\mid j \mid -1}^{\mid j\bigcup S_u \mid - \mid j \mid -1 } u \notin j\]
復雜度 \(O(2^n * n^2)\),這看起來已經很優秀了。
繼續觀察,發現每個狀態的最大獨立集是唯一的
記\(S_j\)表示不能選的點集為j時最大的獨立集大小
每次更新\(S_j\)時將\(F_j\)清零即可
復雜度變為\(O(2^n*n)\)
Code
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int Mod=998244353; int n,m,cou[1500000],state[50],num[1500000]; long long J[50],dp[1500000],R[50]; long long fpow(long long a,int k) { long long ans=1; while (k) { if (k&1) ans=ans*a%Mod; a=a*a%Mod; k>>=1; } return ans; } long long A(int n,int m) { return (n>=m?J[n]*R[n-m]%Mod:0); } int main() { scanf("%d%d",&n,&m); for (int i=1;i<(1<<n);i++) cou[i]=cou[i>>1]+(i&1); for (int i=1;i<=n;i++) state[i]|=1<< i-1; for (int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v); state[u]|=1<< v-1; state[v]|=1<< u-1; } J[0]=1; for (int i=1;i<=n;i++) J[i]=J[i-1]*i%Mod; for (int i=0;i<=n;i++) R[i]=fpow(J[i],Mod-2); dp[0]=1;num[0]=0; for (int i=0;i<(1<<n);i++) { if (!dp[i]) continue; for (int j=1;j<=n;j++) { if ((i>>(j-1)) & 1) continue; if (num[i]+1>num[i|state[j]]) num[i|state[j]]=num[i]+1,dp[i|state[j]]=0; if (num[i]+1==num[i|state[j]]) dp[i|state[j]]=(dp[i|state[j]]+dp[i]*A(n-cou[i]-1,cou[i|state[j]]-cou[i]-1)%Mod)%Mod; } } printf("%lld\n",dp[(1<<n)-1]*fpow(J[n],Mod-2)%Mod); return 0; }
【PKUWC2018】隨機算法