首页 > 技术文章 > 状压DP小拼盘

cervusy 2018-08-16 16:01 原文

有的DP题,某一部分的状态只有两种,选或不选。

开数组记录,代价太大,转移不方便。

状态压缩意为,用 “0/1“ 表示 “选/不选“ 。

把状态表示为二进制整数。

There are 10 kinds of people in the world, who knows binary and who doesn't.

用位运算判断条件并转移状态。

hdu 6149 Valley Numer II

题目传送门

用f[i][j]表示选到前i个点,状态为j的答案。

枚举其他两个高点。

转移之前判断之前是否用过,以及高低点之间是否有连边。

所以用邻接矩阵表示连边比较方便。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 
 6 int t;
 7 int n,m,k;
 8 int c[35][35];
 9 int h[35];
10 int v[35];
11 int f[35][(1<<16)+1];
12 int ans;
13 
14 int main()
15 {
16     scanf("%d",&t);
17     while(t--)
18     {
19         memset(f,0,sizeof(f));
20         memset(c,0,sizeof(c));
21         memset(h,0,sizeof(h));
22         memset(v,0,sizeof(v));
23         scanf("%d%d%d",&n,&m,&k);
24         for(int i=1;i<=m;i++)
25         {
26             int q,w;
27             scanf("%d%d",&q,&w);
28             c[q][w]=1;
29             c[w][q]=1;
30         }
31         for(int i=0;i<k;i++)
32         {
33             scanf("%d",&h[i]);
34             v[h[i]]=1;
35         }
36         int st=(1<<k);
37         ans=0;
38         for(int i=1;i<=n;i++)
39         {
40             for(int j=0;j<st;j++)f[i][j]=f[i-1][j];
41             if(v[i])continue;
42             for(int j=0;j<st;j++)
43             {
44                 for(int q1=0;q1<k;q1++)
45                 {
46                     if(j&(1<<q1))continue;
47                     if(!c[i][h[q1]])continue;
48                     for(int q2=0;q2<q1;q2++)
49                     {
50                         if(j&(1<<q2))continue;
51                         if(!c[i][h[q2]])continue;
52                         f[i][(j|(1<<q1))|(1<<q2)]=max(f[i][(j|(1<<q1))|(1<<q2)],f[i-1][j]+1);
53                     }
54                 }
55             }
56         }
57         for(int j=0;j<st;j++)ans=max(ans,f[n][j]);
58         printf("%d\n",ans);
59     }
60     return 0;
61 }
hdu 6149 Valley Numer II

 

CodeForces 895C Square Subsets

题目传送门

一道数学思想浓重的状压DP。

因为1~70只有19个质数(用pr[]储存每个质数),先预处理出每个数的质因子组成c。

c[i]的第k位表示i这个数含有几个pr[k]因子。含有奇数个,那一位就是1,,否则为零。

f[i][j]表示选到第i个数,此时所有选的数的积的质因子组成为j的时候的方案数。

那个“组成”与之前预处理的表示方法类似,每一个二进制位代表一个质因子的情况,0为偶数个,1为奇数个。

读入的时候记录每个数被读进来的次数。

如果读入的数据中含有x这个数,就对x进行一次计算。

具体来说,x可以选奇数个也可以选偶数个。

偶数个的情况状态不变。奇数个的情况,状态异或上x这个数的组成c[x]。

两个加一起,乘上方案数,取个模即可。

最后因为要求是平方数,所以所有质因子都有偶数个,状态为零。

输出f[nw][0]。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define mod 1000000007
 5 #define ll long long
 6 using namespace std;
 7 
 8 int n;
 9 int h[75];
10 int c[75];
11 int pr[25]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71};
12 ll f[2][1<<20];
13 ll pow[100005];
14 int main()
15 {
16     scanf("%d",&n);
17     for(int i=1;i<=n;i++)
18     {
19         int t;
20         scanf("%d",&t);
21         h[t]++;
22     }
23     for(int i=1;i<=70;i++)
24     {
25         int t=i;
26         int p=0;
27         while(t>1)
28         {
29             while(t%pr[p]==0)
30             {
31                 t/=pr[p];
32                 c[i]^=(1<<p);
33             }
34             p++;
35         }
36     }
37     pow[0]=1;
38     for(int i=1;i<=n;i++)pow[i]=(pow[i-1]<<1)%mod;
39     f[0][0]=1;
40     int nw=0;
41     for(int i=1;i<=70;i++)
42     {
43         if(!h[i])continue;
44         nw^=1;
45         for(int j=0;j<(1<<19);j++)
46         {
47             f[nw][j]=(f[nw^1][j^c[i]]+f[nw^1][j])%mod*pow[h[i]-1]%mod;
48         }
49     }
50     printf("%d",f[nw][0]-1);
51     return 0;
52 }
CodeForces 895C Square Subsets

 

CodeForces 482C Game with Strings

题目传送门

一道期望+状压DP......

摧残大脑的推导与状态转移。

强荐zhx大佬的题解,写的非常非常明白。zhx大佬的题解传送门

作为蒟蒻我就只能大致解释解释代码了QwQ

f[i]代表问了状态为i的问题时,距离确定字符串(终点结果)的期望次数。

显然,f[一个能确定字符串的问法]=0。

接下来的难点在于如何判断每种问法不能确定哪些字符串。

设dbt[i]代表问法为i时,不能区分的字符串有哪些。

用二进制数表示,每一位代表一个字符串,1代表不能确定,0反之。

num[i]代表问法为i时,不能区分的串的个数,也就是dbt[i]中1的个数。

接下来玄学公式和位运算转移状态,zhx大佬的题解里写的很详细,本蒟蒻在此不做赘述。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define ll long long
 5 using namespace std;
 6 
 7 int n,m;
 8 char s[55][25];
 9 ll dbt[1<<20];
10 int num[1<<20];
11 double f[1<<20];
12 
13 int main()
14 {
15     scanf("%d",&n);
16     for(int i=0;i<n;i++)
17     {
18         scanf("%s",s[i]);
19     }
20     if(n==1){printf("0.000000000");return 0;}
21     m=strlen(s[1]);
22     for(int i=0;i<n;i++)
23     {
24         for(int j=i+1;j<n;j++)
25         {
26             ll st=0;
27             for(int k=0;k<m;k++)
28             {
29                 if(s[i][k]==s[j][k])st|=(1ll<<k);
30             }
31             dbt[st]|=(1ll<<i);
32             dbt[st]|=(1ll<<j);
33         }
34     }
35     for(int i=(1<<m)-1;i>=1;i--)
36     {
37         for(int j=0;j<m;j++)
38         {
39             if(i&(1<<j))dbt[i^(1<<j)]|=dbt[i];
40         }
41     }
42     for(int i=0;i<(1<<m);i++)
43     {
44         for(int j=0;j<n;j++)
45         {
46             if(dbt[i]&(1ll<<j))num[i]++;
47         }
48     }
49     f[(1<<m)-1]=0.00;
50     for(int i=(1<<m)-2;i>=0;i--)
51     {
52         if(!num[i]){f[i]=0.00;continue;}
53         int tot=m;
54         for(int j=0;j<m;j++)
55         {
56             if(i&(1<<j))tot--;
57         }
58         for(int j=0;j<m;j++)
59         {
60             if(i&(1<<j))continue;
61             f[i]+=f[i|(1<<j)]*1.0/(double)(tot)*(double)(num[i|(1<<j)])/(double)(num[i]);
62         }
63         f[i]+=1.00;
64     }
65     printf("%.9lf",f[0]);
66     return 0;
67 }
CodeForces 482C Game with Strings

 

hdu 6125 Free from square

题目传送门

由于对于一个合法的答案,sqrt(500)后的素数最多只会用到一个,所以只对前8个素数状压后01背包。 

f(S, k)表示素数状态为S时由k个自然数组成的方法有几种。 

再对后面的素数分别01背包。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define mod 1000000007
 5 #define ll long long
 6 using namespace std;
 7 
 8 int t,n,K;
 9 int num[1<<8];
10 bool pr[505];
11 ll f[1<<8][505];
12 ll ans;
13 
14 int main()
15 {
16     num[0]=1;
17     int k=0;
18     for(int i=2;i<505;i++)
19     {
20         if(!pr[i])
21         {
22             if(k<8)num[1<<k]=i,k++;
23             for(int j=i<<1;j<505;j+=i)pr[j]=1;
24         }
25     }
26     for(int i=1;i<(1<<8);i++)
27     {
28         int j=(i-1)&i;
29         num[i]=num[j]*num[i^j];
30         if(num[i]>505)num[i]=505;
31     }
32     scanf("%d",&t);
33     while(t--)
34     {
35         scanf("%d%d",&n,&K);
36         memset(f,0,sizeof(f));
37         f[0][0]=1;
38         for(int i=0;i<(1<<8);i++)
39         {
40             if(num[i]<=n)
41             {
42                 int ss =((1<<8)-1)^i;
43                 for(int k=K;k>=1;k--)
44                 for(int j=ss;;j=(j-1)&ss)
45                 {
46                     f[i|j][k]=(f[i|j][k]+f[j][k-1])%mod;
47                     if(j==0)break;
48                 }
49                }
50         }
51         for(int i=23;i<=n;i++)
52         {
53             if(pr[i])continue;
54             for(int k=K;k>0;k--)
55             {
56                 for(int j=0;j<(1<<8);j++)
57                 {
58                     if(num[j]*i<=n)
59                     {
60                         int ss=((1<<8)-1)^j;
61                         for(int s=ss;;s=(s-1)&ss)
62                         {
63                             f[j|s][k]=(f[s][k-1]+f[j|s][k])%mod;
64                             if(s==0)break;
65                         }
66                     }
67                 }
68             } 
69         }
70         ans=0;
71         for(int i=0;i<(1<<8);i++)
72             for(int k=K;k>=1;k--)
73                 ans=(ans+f[i][k])%mod;
74         printf("%lld\n",ans);
75     }
76     return 0;
77 }
hdu 6125 Free from square

 

CodeForces 449D Jzzhu and Numbers

题目传送门

传说中的官方题解:

(如果图看起来长宽比不对,查看原图即可)

想法不是很简单。

脑洞大,真的想不出来。

看了题解瞎打了一会,cf测,过不了test5。

盯着code看了半个小时,发现是a数组开小了,少打了个0。

话说a数组本来也没什么用是吧。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define ll long long
 5 #define mod 1000000007
 6 using namespace std;
 7 
 8 int n;
 9 int a[1000005];
10 int f[1<<20][20];
11 
12 ll ksm(int B,int p)
13 {
14     ll ret=1;
15     ll b=B;
16     while(p)
17     {
18         if(p&1)ret=(ret*b)%mod;
19         b=(b*b)%mod;
20         p>>=1;
21     }
22     return ret;
23 }
24 
25 int cnt(int x)
26 {
27     int ret=0;
28     while(x)x-=(x&(-x)),ret++;
29     return (ret&1)?-1:1;
30 }
31 
32 int main()
33 {
34     scanf("%d",&n);
35     for(int i=1;i<=n;i++)scanf("%d",&a[i]),f[a[i]][0]++;
36     for(int i=1;i<=20;i++)
37     {
38         for(int j=0;j<(1<<20);j++)
39         {
40             if(1&(j>>(i-1)))f[j][i]=f[j][i-1];
41             else f[j][i]=f[j][i-1]+f[j+(1<<(i-1))][i-1];
42         }
43     }
44     ll ans=0;
45     for(int i=0;i<(1<<20);i++)
46     {
47         ll tmp=((ksm(2,f[i][20])-1)*cnt(i)%mod+mod)%mod;
48         ans=(ans+tmp+mod)%mod;
49     }
50     printf("%I64d",ans);
51     return 0;
52 }
CodeForces 449D Jzzhu and Numbers

 

hdu 5713 K个联通块

题目传送门

跟前面某些题相比还是比较友善的。

状态不是很难想,转移也是显然,就是中间的步骤比较多,倒起来有些复杂。

首先算出num[i]表示选点为i状态时,i中这些点相互连边的方案数。

具体做法是找出lowbit代表的那个点,跟前面的点一一计算累加到答案上。

再算出f[i][p]代表所选点集为i时,构成p个联通块的方案数。

第一步要算出f[i][1]。

利用容斥原理,用所有的方法减去不合法的方法。

再利用f[i][1]算出f[i][2...k]。

最后的答案为f[所有点都选((1<<n)-1)][k]。

注意取模。

模数很奇葩。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define ll long long
 5 #define mod 1000000009
 6 using namespace std;
 7 
 8 int t,n,m,k;
 9 int e[20][20],num[1<<14];
10 ll f[1<<14][20];
11 
12 int count(int nw,int p)
13 {
14     int ret=0;
15     for(int i=1;i<=n;i++)
16     {
17         if((1<<(i-1))!=p)continue;
18         p=i;
19         break;
20     }
21     for(int i=1;i<=n;i++)
22     {
23         if(nw&(1<<(i-1)))ret+=e[i][p];
24     }
25     return ret;
26 }
27 
28 int main()
29 {
30     scanf("%d",&t);
31     for(int cs=1;cs<=t;cs++)
32     {
33         memset(e,0,sizeof(e));
34         memset(num,0,sizeof(num));
35         memset(f,0,sizeof(f));
36         scanf("%d%d%d",&n,&m,&k);
37         for(int i=1;i<=m;i++)
38         {
39             int v1,v2;
40             scanf("%d%d",&v1,&v2);
41             e[v1][v2]=e[v2][v1]=1;
42         }
43         for(int i=1;i<(1<<n);i++)
44         {
45             int lb=i&(-i);
46             num[i]=num[i^lb]+count(i,lb);
47         }
48         for(int i=1;i<(1<<n);i++)
49         {
50             int lb=i&(-i);
51             ll no=0;
52             for(int j=i^lb;j;j=((j-1)&(i^lb)))
53             {
54                 no=(no+f[i^j][1]*1ll%mod*(1ll<<num[j])%mod)%mod;
55             }
56             f[i][1]=(1ll*(1ll<<num[i])%mod-no)%mod;
57         }
58         for(int i=1;i<(1<<n);i++)
59         {
60             for(int j=2;j<=k;j++)
61             {
62                 int lb=i&(-i);
63                 for(int h=i^lb;h;h=((h-1)&(i^lb)))
64                 {
65                     f[i][j]=(f[i][j]+f[h][j-1]*f[i^h][1]%mod)%mod;
66                 }
67             }
68         }
69         printf("Case #%d:\n%lld\n",cs,(f[(1<<n)-1][k]%mod+mod)%mod);
70     }
71     return 0;
72 }
hdu 5713 K个联通块

 

推荐阅读