1. 程式人生 > >LOJ2540 [PKUWC2018] 隨機算法 【狀壓DP】

LOJ2540 [PKUWC2018] 隨機算法 【狀壓DP】

can dfs 枚舉 for 狀壓dp clu 成了 方案 能夠

題目分析:

聽說這題考場上能被$ O(4^n) $的暴力水過,難不成出題人是畢姥爺?

首先思考一個顯而易見的$ O(n^2*2^n) $的暴力DP。一般的DP都是考慮最近的加入了哪個點,然後刪除後遞歸進行狀壓DP。由於這道題的題目詢問方式是反過來的,處理方式也反過來。

令$ f[n][S] $表示當前有$ S $這些點,期望這些點能夠構成獨立集大小為$ n $。正向的考慮選擇了哪個點,並把與這個點有連邊的所有點在集合內進行刪除,令找到的新狀態為$ f[n-1][P] $。我們把$ P $中的結點與$ S $中不在$ P $中的點進行標號拼接。寫成語言就是$ f[n][S]+=f[n-1][P] * \binom{|S|-1}{|P|} * |P|! $ 由於對於$ S $的每一個點都需要轉移一遍,時間復雜度就變成了$ O(n^2*2^n) $。雖然跑得不快,但是由於冗余狀態較多,考場上一定比例的人使用這個算法通過了這個題。

現在來考慮把它優化到$ O(n*2^n) $。由於題目期望著你獲得一個最大獨立集,所以我們可以發現第一維是沒有必要的。因為對於一個目標狀態$ S $,我們如果知道$ S $對應的最大獨立集的大小的話,那麽我們必定是奔著這個大小而去的。現在我們用$ g[S] $來表示$ S $對應的最大獨立集大小,那麽這是一個普及組題目,枚舉選點然後求max就行了。再對於$ f[S] $,求$ S $對應的最大獨立集大小。首先記錄$ g[S] $,然後找刪除某個點後的集合變為了$ g[S]-1 $的就是我們想要的轉移方案,同樣采用帶標號的拼接。因為我們沒有了第一維的負擔,所以時間復雜度驟降為了$ O(n*2^n) $.

ps:我終於會用letax數學公式啦。

代碼:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int maxn = 25;
 5 const int mod = 998244353;
 6 
 7 int n,m;
 8 int connect[maxn];
 9 int f[(1<<20)+5],g[(1<<20)+5],sz[(1<<20)+5];
10 int arr[(1<<20)+5],C[maxn][maxn],fac[25];
11 
12
int fast_pow(int now,int pw){ 13 if(pw == 1) return now; 14 int z = fast_pow(now,pw/2); 15 z = (1ll*z*z) % mod; 16 if(pw & 1) z = (1ll*z*now)%mod; 17 return z; 18 } 19 20 void read(){ 21 scanf("%d%d",&n,&m); 22 for(int i=1;i<=m;i++){ 23 int u,v; scanf("%d%d",&u,&v); 24 connect[u] |= (1<<v-1); 25 connect[v] |= (1<<u-1); 26 } 27 for(int i=1;i<=n;i++) connect[i] |= (1<<i-1); 28 } 29 30 void init(){ 31 C[0][0] = 1; 32 for(int i=1;i<=n;i++){ 33 C[i][0] = C[i][i] = 1; 34 for(int j=1;j<i;j++) C[i][j] = (C[i-1][j-1]+C[i-1][j])%mod; 35 } 36 for(int i=1;i<(1<<n);i++){ 37 for(int j=0;j<n;j++) if((1<<j)&i) sz[i]++; 38 } 39 fac[0] = 1; 40 for(int i=1;i<=n;i++) fac[i] = (1ll*fac[i-1]*i)%mod; 41 } 42 43 void dfs(int now){ 44 arr[now] = 1; 45 for(int i=1;i<=n;i++){ 46 if(!((1<<i-1)&now)) continue; 47 int p = now - (now&connect[i]); 48 if(!arr[p]) dfs(p); 49 g[now] = max(g[now],g[p]+1); 50 } 51 } 52 53 void dfs2(int now){ 54 arr[now] = 1; 55 for(int i=1;i<=n;i++){ 56 if(!((1<<i-1)&now)) continue; 57 int kk = (now&connect[i]),p = now - kk; 58 if(g[p] != g[now]-1) continue; 59 if(!arr[p]) dfs2(p); 60 f[now]+=((1ll*C[sz[now]-1][sz[p]]*fac[sz[kk]-1])%mod)*f[p]%mod; 61 f[now] %= mod; 62 } 63 } 64 65 void work(){ 66 init(); 67 g[0] = 0; arr[0] = 1; 68 for(int i=1;i<(1<<n);i++) if(!arr[i]) dfs(i); 69 memset(arr,0,sizeof(arr)); 70 f[0] = 1; arr[0] = 1; 71 dfs2((1<<n)-1); 72 int ans = f[(1<<n)-1]; 73 ans = (1ll*fast_pow(fac[n],mod-2)*ans)%mod; 74 printf("%d",ans); 75 } 76 77 int main(){ 78 read(); 79 work(); 80 return 0; 81 }

LOJ2540 [PKUWC2018] 隨機算法 【狀壓DP】