zoukankan      html  css  js  c++  java
  • 状态压缩动态规划总结

    状态压缩是一个很广的概念,在OI中也有很多的应用,当我们把他应用到动态规划中,可以用来精简状态,节约空间,也方便转移。最常见的就是用二进制来表是状态,利用各种位移运算,就可以实现(O(1))的转移。状压DP适用于“窄棋盘”上的DP,否则状态太多无法存下。

     POJ1185 炮兵阵地

    题意:给一个(N imes M)的地形盘,有平原和山坡,要求在平原上部署尽量多的炮(攻击范围2),使得不互相攻击。

    数据范围:N <= 100;M <= 10,符合条件。如何表示状态?按行DP,一个二进制数(m<=10,int足够了,最大状态数:(1 << 10) = 1024,但有很多不合法状态)上的某位等于1表示在此处有炮。用F[i][j][k]表示在i行,当前行状态为j,前一列状态为k时的最优解。二进制的优势在此时体现出来了:我可以(O(1))判断状态是否合法,详见代码,此处需要深入体会。

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 using namespace std;
     5 
     6 const int maxn = 60 + 10;
     7 
     8 int G[maxn*2],State[maxn],Num[maxn*2],dp[maxn*2][maxn][maxn];
     9 
    10 int main()
    11 {
    12     int n,m;
    13     int Snum=0;
    14 
    15     scanf("%d%d
    ",&n,&m);
    16     for(int i=0;i<n;++i)
    17     {
    18         for(int j=0;j<m;++j)
    19         {
    20             char c=getchar();
    21             if(c=='H')
    22                 G[i]+=1<<j;
    23         }
    24         getchar();
    25     }
    26 
    27     for(int i=0;i<(1<<m);++i)
    28     {
    29         if((i&(i<<1)) || (i&(i<<2)))
    30             continue;
    31         int k=i;
    32         while(k)
    33         {
    34             Num[Snum]+=k&1;
    35             k>>=1;
    36         }
    37         State[Snum++]=i;
    38     }
    39 
    40     for(int i=0;i<Snum;++i)
    41     {
    42         if(State[i]&G[0])
    43             continue;
    44         dp[0][i][0]=Num[i];
    45     }
    46 
    47     for(int i=0;i<Snum;++i)
    48     {
    49         if(State[i]&G[1])
    50             continue;
    51         for(int j=0;j<Snum;++j)
    52         {
    53             if((State[i]&State[j]) || (State[j]&G[0]))
    54                 continue;
    55             dp[1][i][j]=max(dp[1][i][j],dp[0][j][0]+Num[i]);
    56         }
    57     }
    58     
    59     for(int i=2;i<n;++i)
    60     {
    61         for(int j=0;j<Snum;++j)
    62         {
    63             if(State[j]&G[i])
    64                 continue;
    65             for(int k=0;k<Snum;++k)
    66             {
    67                 if(State[k]&State[j])
    68                     continue;
    69                 if(State[k]&G[i-1])
    70                     continue;
    71                 for(int p=0;p<Snum;++p)
    72                 {
    73                     if(State[p]&G[i-2])
    74                         continue;
    75                     if(State[p]&State[k])
    76                         continue;
    77                     if(State[p]&State[j])
    78                         continue;
    79                     dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][p]+Num[j]);
    80                 }
    81             }
    82         }
    83     }
    84 
    85     int ans=0;
    86     for(int i=0;i<Snum;++i)
    87         for(int j=0;j<Snum;++j)
    88             ans=max(ans,dp[n-1][i][j]);
    89     printf("%d
    ",ans);
    90     return 0;
    91 }
    View Code

    POJ3254 Corn Fields

    题意:一个row*col的矩阵,每个格子是0或者1,1表示土壤肥沃可以种植草地,0则不可以。在种草地的格子可以放牛,但边相邻的两个格子不允许同时放牛,问总共有多少种放牛的方法?(不放牛也算一种情况)

    数据范围:1 ≤ M ≤ 12; 1 ≤ N ≤ 12,同样的小棋盘模型。做法其实都差不多,关键是状态的转移,这道题比上一道题要容易些。

     1 #include <cstdio>
     2 #include <cstring>
     3 const int maxn = 15;
     4 const int maxs = 5000;
     5 const int Mod = 1e8;
     6 
     7 int State[maxs],Snum;
     8 int G[maxn];
     9 int dp[maxn][maxs];
    10 int n,m;
    11 
    12 void init()
    13 {
    14     for(int i=0;i<(1<<m);++i)
    15         if(!(i&(i<<1)))
    16             State[Snum++]=i;
    17 }
    18 
    19 int main()
    20 {
    21     freopen("data.out","w",stdout);
    22     freopen("data.in","r",stdin);
    23         
    24     scanf("%d%d",&n,&m);
    25     for(int i=0;i<n;++i)
    26         for(int j=0;j<m;++j)
    27         {
    28             int t;
    29             scanf("%d",&t);
    30             if(t==0)
    31                 G[i]+=(1<<j);
    32         }
    33     init();
    34     
    35     for(int i=0;i<Snum;++i)
    36         if(!(State[i]&G[0]))
    37             dp[0][i]=1;
    38 
    39     for(int i=1;i<n;++i)
    40         for(int j=0;j<Snum;++j)
    41         {
    42             if((State[j]&G[i]))
    43                 continue;
    44             for(int k=0;k<Snum;++k)
    45             {
    46                 if((State[k]&G[i-1]) || (State[k]&State[j]))
    47                     continue;
    48                 dp[i][j]=(dp[i][j]+dp[i-1][k])%Mod;
    49             }
    50         }
    51     int ans=0;
    52     for(int i=0;i<Snum;++i)
    53         ans=(ans+dp[n-1][i])%Mod;
    54     printf("%d
    ",ans);
    55     return 0;
    56 }
    View Code

    BZOJ1087: [SCOI2005]互不侵犯King

    题意:在N×N的棋盘里面放K个国王,使他们互不攻击,求共有多少种摆放方案。

    数据范围:1 <=N <=9, 0 <= K <= N * N,好小的N。。。国王的攻击范围是什么?想清楚了就是sb题了。

    贴一个以前写的题解:

    当前状态只会影响下一行的状态。用f[i][j][k]表示行i处状态为j时使用k个棋子可行的方案数。判状态转移时,两个状态S1与S2融洽的条件是!((S1<<1)&S2) && !((S1>>1)&S2) && !(S1&S2),且S1本身可行的条件是:!((S1<<1)&S1) && !((S1>>1)&s1)。

    位运算真是个好东西。。

     1 #include <cstdio>
     2 #include <cstring>
     3 
     4 const int maxn = 512 + 20;
     5 
     6 typedef long long LL;
     7 
     8 LL f[10][maxn][maxn];
     9 bool ok[10][maxn];
    10 int main()
    11 {
    12 
    13     int n,m;
    14     scanf("%d%d",&n,&m);
    15 
    16     memset(f,0,sizeof f);
    17     memset(ok,false,sizeof ok);
    18 
    19     f[0][0][0]=1;
    20     ok[0][0]=true;
    21     for(int i=1;i<=n;++i)
    22     {
    23         for(int j=0;j<(1<<n);++j)
    24         {
    25             if(ok[i-1][j])
    26             {
    27                 for(int k=0;k<(1<<n);++k)
    28                 {
    29                     if(((k<<1)&k) || ((k>>1)&k)) continue;
    30                     if((j&k) || ((j<<1)&k) || ((j>>1)&k)) continue;
    31                     int tmp=k,cnt=0;
    32                     while(tmp)
    33                     {
    34                         if(tmp&1)
    35                             ++cnt;
    36                         tmp>>=1;
    37                     }
    38                     ok[i][k]=true;
    39                     for(int p=0;p+cnt<=m;++p)
    40                         f[i][k][p+cnt]+=f[i-1][j][p];
    41                 }
    42             }
    43         }
    44     }
    45     
    46     LL ans=0;
    47     for(int j=0;j<(1<<n);++j)
    48         ans+=f[n][j][m];
    49     printf("%lld
    ",ans);
    50     return 0;
    51 }
    View Code

    看了3道棋盘规划型的题,我们来看一些棋盘覆盖的例子。

    POJ2411 Mondriaan's Dream

    题意:一个h*w的矩阵,希望用2*1或者1*2的矩形来填充满,求填充的总方案数。

    数据范围:1<=h,w<=11。这道题是非常经典的棋盘覆盖状压DP。讲一个优化:先将可以兼容的状态dfs出来。详见代码。

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #include <vector>
     5 using namespace std;
     6 
     7 const int maxn = 14;
     8 const int maxs = 40000;
     9 typedef unsigned long long UI;
    10 
    11 int n,m;
    12 vector<int> State[maxs];
    13 int Snum;
    14 UI dp[maxn][maxs];
    15 
    16 void dfs(int col,int S1,int S2)
    17 {
    18     if(col>=m)
    19     {
    20         if(S1<(1<<m) && S2<(1<<m))
    21             State[S2].push_back(S1);
    22         return;
    23     }
    24     dfs(col+1,(S1<<1)|1,S2<<1);
    25     dfs(col+1,S1<<1,(S2<<1)|1);
    26     dfs(col+2,(S1<<2)|3,(S2<<2)|3);
    27 }
    28 
    29 void solve()
    30 {
    31     Snum=1<<m;
    32     memset(dp,0,sizeof dp);
    33     for(int i=0;i<Snum;++i)
    34         State[i].clear();
    35     dfs(0,0,0);
    36     dp[0][0]=1;
    37     for(int i=1;i<=n+1;++i)
    38         for(int j=0;j<Snum;++j)
    39             for(int k=0,sz=State[j].size();k<sz;++k)
    40                 dp[i][j]+=dp[i-1][State[j][k]];
    41     printf("%lld
    ",dp[n+1][Snum-1]);
    42 }
    43 
    44 int main()
    45 {
    46     while(scanf("%d%d",&n,&m)!=EOF && n!=0 && m!=0)
    47     {
    48         if((n&1) && (m&1))
    49         {
    50             puts("0");
    51             continue;
    52         }
    53         if(n<m)
    54             swap(n,m);
    55         solve();
    56     }
    57     return 0;
    58 }
    View Code

    SGU131 Hardwood floor

    题意:在(N imes M)的棋盘内放1*2及2*2去掉一个角的块,问方案数。

    数据范围:1<=M<=9, 1<=N<=9。原理是一样的,同样可以dfs出兼容的状态。但是具体如何实现还是有问题的,因为第二种块太奇葩了(第二种块:我tm哪里奇葩啦!)。dfs多传入两个参数:f1, f2,代表上一列摆放块对当前的影响。

     1 #include <string>
     2 #include <bitset>
     3 #include <cstdio>
     4 #include <cstring>
     5 #include <algorithm>
     6 #include <vector>
     7 using namespace std;
     8 
     9 const int maxn = 11;
    10 const int maxs = 1000;
    11 
    12 typedef long long LL;
    13 
    14 LL dp[maxn][maxs];
    15 vector<int> State[maxs];
    16 int n,m,Snum;
    17 
    18 bitset<13> tmp;
    19 void dfs(int col, int s1, int s2, int f1, int f2) {
    20     if(m <= col) {
    21         if(!f1 && !f2) {
    22 /*
    23             tmp = s1;
    24             printf("%s ", tmp.to_string().c_str());
    25             tmp = s2;
    26             printf("%s ", tmp.to_string().c_str());
    27             tmp = s2 ^ (Snum - 1);
    28             printf("%s
    ", tmp.to_string().c_str());
    29 */
    30             State[s2 ^ (Snum - 1)].push_back(s1);
    31         }
    32         return ;
    33     }
    34     dfs(col + 1, (s1 << 1) + f1, (s2 << 1) + f2, 0, 0);
    35     // 可以尝试把上面那句话去掉
    36     // 。。。然后你就只能得到一种状态:放满了的。。
    37     if(!f1) {
    38         if(!f2) {
    39             dfs(col + 1, (s1 << 1) + 1, (s2 << 1) + 1, 0, 0);
    40             dfs(col + 1, (s1 << 1) + 1, (s2 << 1) + 1, 1, 0);
    41             dfs(col + 1, (s1 << 1) + 1, (s2 << 1) + 1, 0, 1);
    42         }
    43         dfs(col + 1, (s1 << 1) + 1, (s2 << 1) + f2, 1, 0);
    44         dfs(col + 1, (s1 << 1) + 1, (s2 << 1) + f2, 1, 1);
    45     }
    46     if(!f2) {
    47         dfs(col + 1, (s1 << 1) + f1, (s2 << 1) + 1, 1, 1);
    48     }
    49 }
    50 
    51 LL solve() {
    52     dp[0][Snum-1]=1;
    53     for(int i=1;i<=n;++i)
    54         for(int j=0;j<Snum;++j)
    55             for(int k=0,sz=State[j].size();k<sz;++k)
    56                 dp[i][j]+=dp[i-1][State[j][k]];
    57     return dp[n][Snum-1];
    58 }
    59 
    60 int main() {
    61     scanf("%d%d",&n,&m);
    62     if(n<m)
    63         swap(n,m);
    64     Snum=1<<m;
    65     dfs(0, 0, 0, 0, 0);
    66     printf("%lld
    ",solve());
    67     return 0;
    68 }
    View Code

    SGU132 Another Chocolate Manic

    详见此处:http://www.cnblogs.com/hzf-sbit/p/3870652.html

    OK,以上就是本人最近(呵呵)做的几道状态压缩DP题。

    然后状态压缩还有个很好玩的东东:基于联通性的状态压缩动态规划,俗称:插头DP,以后再总结。。。

    PS:欢迎指教~~

  • 相关阅读:
    vue 设置初始值 为null 进行文字长度判断
    js ajax传参 定义的参数和传的参数不一样
    js 删除字符串前几位字符,和删除最后一个字符
    js splice 删除数组 相邻的数据满足条件未被删掉
    js 将对象增加新键值对
    js 将数组添加key
    js 删除掉数组中的 ' '
    js 如何删除数组中指定对象及其值
    接口
    分支结构
  • 原文地址:https://www.cnblogs.com/hzf-sbit/p/3870716.html
Copyright © 2011-2022 走看看