LOJ2540 [PKUWC2018] 随机算法 【状压DP】
题目分析:
听说这题考场上能被$ 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|} * |S-P-1|! $ 由于对于$ 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 }