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 }

 

posted @ 2018-05-20 22:20  menhera  阅读(205)  评论(0编辑  收藏  举报