当前位置:   article > 正文

状态压缩DP入门(算入了个门吧)_在一个n*m(n<=5,m<=1000)的棋盘,现在有1*2和2*1(1行2列或2行1列)的小木块无

在一个n*m(n<=5,m<=1000)的棋盘,现在有1*2和2*1(1行2列或2行1列)的小木块无数,要

8/1多校赛1003血虐我  非得学会状压DP不可

感谢博主无私分享

 

状态压缩鼻祖: n皇后问题

描述:

传统做法是对每一行 i,暴力查找每一列 j 。 主要浪费时间在于检查是否有皇后会攻击,又花费o(n)。因此一共是o(n^3)。

改进算法引入状态压缩,依旧是每行i , 每列j 跑不掉的 o(n^2) ,不过判断上面可以用位运算花费o(1),实现复杂度降低

那么位运算是如何用于判断某个位置上是否收到攻击的呢? 举个例子吧

第1行放置的情况   1 0 0 0

第2                        0  0 1 0

 

结合前两行,第3行在这些位置上在竖直方向上已经不能再放了( 约束条件 ) 1 0 1 0。按照这个思想,我们再算出主对角线上的约束条件,副对角线上的约束条件,通过总约束条件的约束,剩下的点就是可以放皇后的合法位置, 我们依次dfs这些合法位置就行。

所以现在问题就在于如何计算这些约束条件,这里有篇很好的文章(再次感谢博主无私分享,上面是我看完自己的理解,可以移步去这个链接了)下面记录转载过来的位运算笔记用自己的话理解

   在状态压缩中,通常用 ( 1 << N ) - 1 来表示最大状态MAXST(全部都是1 ,11111111),用 A | B 来合并A和B的状态位,用  A & ~B 来清除A状态中所有的B中的状态位。

一、DFS函数参数Col,MainDiag,ContDiag的表示意义:

      上面讲的,前 i行上的总列约束条件、主对角线约束、副对角线约束

二、Col == ( 1 << N ) - 1 的表示意义:

      前i行把所有列上的总约束条件占满了 ,那么就是全摆上1了啊,也就是说找到了一种方案

三、emptycol = ( ( 1 << N ) - 1 ) & ~( Col | MainDiag | ContDiag ) 的表示意义:

      111111 & (~ 00010) 则得到 1111101 ,  相当于扣掉了总约束条件,剩下的合法序列

四、curcol = ( emptycol ) & ( ( ~emptycol ) + 1 ) 的表示意义:

     也就是 lowbit = x & (-x) ,补码等于反码+1 , 比如说x等于 110 则 -x : 010 , 因此lowbit = 010,得到最右边的合法位置。   也就是用于遍历合法序列

五、 emptycol &= ~curcol 的表示意义:

    类似于三,当前选中了curcol, 那么合法序列就扣掉当前位置

六、DFS递归的函数参数 Col | curcol,( MainDiag | curcol ) >> 1,( ContDiag | curcol ) << 1 的表示意义:

  第一个参数在上面已经讲过了,现在讲第二个参数, 我们还是分析第1行对第2行的影响。

例如: 1 ->   0 1 0 0 0

第二行怎么找到自己主对角线上的 约束呢 ,其实只要把 第一行右移一位,然后照着第一个参数的方法累加,得到的就是对第二行的约束。 因为  (x,y) 的主对角线约束是 (x-1, y-1)。

第三个参数也就类似

 

以上都是本焫鸡通过自己能理解的方式理解的,要是有什么不对的地方请指出

代码:

  1. // 位运算
  2. #include<stdio.h>
  3. #include <iostream>
  4. using namespace std;
  5. int n,high,ans;
  6. // col 是前i-1行摆放的皇后 联合起来对 第i行的约束
  7. // fir 前i-1行 摆放的皇后,在主对角线方向上,对i行的约束
  8. // sec 是前i-1行摆放的皇后,在负对角线上对第i行的约束
  9. void dfs(int col,int fir,int sec)
  10. {
  11. if(col == high)
  12. {
  13. ans++;
  14. return;
  15. }
  16. int cur = high & (~(col|fir|sec)); //11111的情况 剔除掉 三座大山联合约束的情况 ,得到的序列, 存在某位为1 表示该位可以放
  17. while(cur)
  18. {
  19. int lowbit = cur &(-cur); //得到最后一个位置
  20. dfs(col|lowbit, (fir|lowbit)>>1 , (sec|lowbit)<<1);
  21. cur = cur & (~lowbit); // 最后一个1 标记为1 。 配合while循环。相当于把所以可能性遍历了一遍
  22. }
  23. }
  24. int main()
  25. {
  26. while(~scanf("%d",&n)&&n)
  27. {
  28. ans=0;
  29. high = (1<<n)-1;
  30. dfs(0,0,0);
  31. printf("%d\n",ans);
  32. }
  33. return 0;
  34. }

    

带权类n皇后问题

求到当前所用的列是第几位,然后带回去计算值,记忆话搜索剪枝

代码:

  1. #include <iostream>
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. const int maxn = 100;
  5. int mp[maxn][maxn], dp[maxn][maxn];
  6. int n,ans, high;
  7. int dfs(int col,int tmp, int i)
  8. {
  9. if(dp[col][i] != -1) return dp[col][i];
  10. if(col == high)
  11. {
  12. return dp[col][i] = tmp;
  13. }
  14. int last = high& (~col);
  15. int ans2 = 0;
  16. while(last)
  17. {
  18. int lowbit = last & (-last); // 得到最低位的1 的位置
  19. int cnt = log2(lowbit)+1;
  20. // cout<<cnt<<endl;
  21. ans2 = max(tmp,dfs(col|lowbit, tmp + mp[i][n-cnt+1], i+1));
  22. last = last & (~lowbit);
  23. }
  24. return dp[col][i] = ans2;
  25. }
  26. int main()
  27. {
  28. int t;
  29. scanf("%d",&t);
  30. while(t--)
  31. {
  32. memset(dp,-1,sizeof(dp));
  33. ans = 0;
  34. scanf("%d",&n);
  35. high = (1<<n)-1;
  36. for(int i=1; i<=n; i++)
  37. {
  38. for(int j=1; j<=n; j++)
  39. {
  40. scanf("%d",&mp[i][j]);
  41. }
  42. }
  43. printf("%d\n",dfs(0,0,1));
  44. }
  45. return 0;
  46. }

 

 

lightoj 1011 - Marriage Ceremonies

转自这里,感谢博主分享

 

题意:

有N个怪兽,给出H[ ]数组,代表每个怪兽的HP值,给出 mp[ n ][ p ],表示第n个怪兽死了之后,获得它的装备可以对 第p 个怪兽造成的伤害( 每攻击一下 ), 另外,我们还有一把小手枪 每攻击一次 造成 1 伤害,问的是至少攻击几次能把所有怪兽杀死?

思路:

如果用状态压缩保存怪兽存亡状态,那么数组只需要开 1<<16,我们考虑的是杀小怪兽的顺序,和使用哪把武器( 这里无限使用 ),因此我们暴力杀怪兽的顺序,n ! 级别, 每杀一只怪兽都使用效果最好的武器( 贪心 ),因此搜索的结构就是(搜索第一步一定要确立好结构,之后再填上细节,剪枝)

  1. int dfs(int state)
  2. {
  3. for(怪兽:) //选出还活着的
  4. {
  5. for( 武器: ) // 贪心找出效果最好的
  6. {
  7. cos = min ;
  8. }
  9. DP[state] = max(DP[state], dfs(杀完小怪兽的state) + cos);
  10. }
  11. }
  12. main{
  13. dfs(0);
  14. }

我们可以用1 表示怪兽已经死了,这里使用的不是之前的位运算遍历, 因为状态的0 我们也需要考虑, 那么如果仅仅接受状态,而不接收n , 例如 state = 1 , 那么我们无法知道还活着的还有几只,也就是1前面有几个

代码:

  1. #include <iostream>
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. const int maxn = 1000;
  5. const int inf = 0x3f3f3f3f;
  6. int mp[maxn][maxn], dp[maxn], h[maxn];
  7. char st[maxn];
  8. int n,ans, high;
  9. int dfs(int state)
  10. {
  11. if(dp[state] != inf)
  12. return dp[state];
  13. if(state == high)
  14. return dp[state] = 0;
  15. for(int i=1; i<=n; i++) // 选择杀哪个怪物
  16. {
  17. if( !(state& (1<< (i-1) ) ) ) // 找出活的
  18. {
  19. // cout<<"I am alive"<<endl;
  20. int cnt = inf;
  21. for(int j=1; j<=n; j++) // 用哪个枪
  22. {
  23. if(state & (1<< (j-1) ) && mp[j][i] != 0) // 死了的为1 , 那么& 真, 就是能用这把枪
  24. {
  25. // cout<<"you are my gun"<<endl;
  26. int cos = h[i] / mp[j][i] + ((h[i] % mp[j][i])? 1:0) ;
  27. cnt = min(cnt, cos);
  28. }
  29. }
  30. if(cnt == inf)
  31. {
  32. cnt = h[i];
  33. }
  34. dp[state] = min(dp[state], dfs(state | ( (1<<i-1) )) + cnt);
  35. }
  36. }
  37. return dp[state] ;
  38. }
  39. int main()
  40. {
  41. int t;
  42. scanf("%d",&t);
  43. while(t--)
  44. {
  45. // memset(dp,-1,sizeof(dp));
  46. ans = 0;
  47. scanf("%d",&n);
  48. for(int i=1; i<=n; i++)
  49. scanf("%d",&h[i]);
  50. high = (1<<n) -1;
  51. for(int i=0; i<= high ; i++)
  52. dp[i] = inf;
  53. for(int i=1; i<=n; i++)
  54. {
  55. scanf("%s",st);
  56. for(int j=0; j<n; j++)
  57. {
  58. mp[i][j+1] = st[j]-'0';
  59. }
  60. }
  61. printf("%d\n",dfs(0));
  62. }
  63. return 0;
  64. }

 

 

 

Poj - 3254

题目大意:农夫有一块地,被划分为m行n列大小相等的格子,其中一些格子是可以放牧的(用1标记),农夫可以在这些格子里放牛,其他格子则不能放牛(用0标记),并且要求不可以使相邻格子都有牛。现在输入数据给出这块地的大小及可否放牧的情况,求该农夫有多少种放牧方案可以选择(注意:任何格子都不放也是一种选择,不要忘记考虑!

Sample Input

  1. 2 3
  2. 1 1 1
  3. 0 1 0

Sample Output

9

分析

本题中,假设第i行允许 j 种放牛方式(即j种状态) ,那么对于每一种状态 k (k (= [1, j]),把上一行 i-1 上满足和 k 不冲突的(题目要求上下不能同时为1 )状态累加,相当于每个k对应一系列 i-1 行满足的状态 , 遍历 k 累加就行,直接上代码读起来容易些。

该类型题目由于状态很小,所以每次暴力查找状态。

代码:

  1. #include <cstdio>
  2. #include <cstring>
  3. using namespace std;
  4. #define mod 100000000
  5. const int maxn = 10050;
  6. int M,N,top = 0;
  7. //top表示每行最多的状态数
  8. int state[600],num[110];
  9. //state存放每行所有的可行状态(即没有相邻的状态
  10. //
  11. int dp[20][600];
  12. //dp[i][j]:对于前i行数据,每行有前j种可能状态时的解
  13. int cur[20];
  14. //cur[i]表示的是第i行整行的情况
  15. inline bool ok(int x) //判断状态x是否可行
  16. {
  17. if(x&x<<1)
  18. return false;//若存在相邻两个格子都为1,则该状态不可行
  19. return true;
  20. }
  21. void init() //遍历所有可能的状态
  22. {
  23. top = 0;
  24. int total = 1 << N; //遍历状态的上界
  25. for(int i = 0; i < total; ++i)
  26. {
  27. if(ok(i))
  28. state[++top] = i;
  29. }
  30. }
  31. inline bool fit(int x,int k) //判断状态x 与第k行的实际状态的逆是否有‘重合’
  32. {
  33. if(x&cur[k])
  34. return false; //若有重合,(即x不符合要求)
  35. return true; //若没有,则可行
  36. }
  37. int main()
  38. {
  39. while(scanf("%d%d",&M,&N)!= EOF)
  40. {
  41. init(); // 1 - 1<<N 的所以可行状态保存在state[1-top]中
  42. memset(dp,0,sizeof(dp));
  43. for(int i = 1; i <= M; ++i)
  44. {
  45. cur[i] = 0; // cur[i] 代表第i行草地的情况 1001 还是 0100等等
  46. int num;
  47. for(int j = 1; j <= N; ++j) //输入时就要按位来存储,cur[i]表示的是第i行整行的情况,每次改变该数字的二进制表示的一位
  48. {
  49. scanf("%d",&num); //表示第i行第j列的情况(01
  50. if(num == 0) //若该格为0
  51. cur[i] +=(1<<(N-j)); //则将该位置为1(注意要以相反方式存储,即1表示不可放牧
  52. // 假设输入 1010 那么cur[i]为 1010 , 第一个读入的 左移 4-1
  53. }
  54. }
  55. for(int i = 1; i <= top; i++)
  56. {
  57. if(fit(state[i],1)) //判断所有可能状态与第一行的实际状态的逆是否有重合
  58. {
  59. dp[1][i] = 1; //若第1行的状态与第i种可行状态吻合,则dp[1][i]记为1
  60. }
  61. // 例如第一行本应该是 111 那么转换成了 000 , 则状态101 跟第一行fit为1 ,则dp[1][state - 101] = 1
  62. }
  63. /*
  64. 状态转移过程中,dp[i][k] =Sigma dp[i-1][j] (j为符合条件的所有状态)
  65. dp[i][k] = Sigma dp[i-1][j] (j为满足条件的所有状态)
  66. */
  67. for(int i = 2; i <= M; ++i) //i索引第2行到第M行
  68. {
  69. for(int k = 1; k <= top; ++k) //该循环针对所有可能的状态,找出一组与第i行相符的state[k]
  70. {
  71. if(!fit(state[k],i))
  72. continue; //判断是否符合第i行实际情况
  73. for(int j = 1; j <= top ; ++j) //找到state[k]后,再找一组与第i-1行符合,且与第i行(state[])不冲突的状态state[j]
  74. {
  75. if(!fit(state[j],i-1))
  76. continue; //判断是否符合第i-1行实际情况
  77. if(state[k]&state[j])
  78. continue; //判断是否与第i行冲突
  79. dp[i][k] = (dp[i][k] +dp[i-1][j])%mod; //若以上皆可通过,则将'j'累加到‘k'上
  80. // dp[i][k] = dp[i][k] + d[i-1][j] 第i行的状态 加上i-1行可用的状态。
  81. // 比如说 i行有两种状态 010 000 那么i就要考虑这两种情况, 当选择第一个010 时, i-1 有四种情况,也就是j有四种
  82. }
  83. }
  84. }
  85. int ans = 0;
  86. for(int i = 1; i <= top; ++i) //累加最后一行所有可能状态的值,即得最终结果!!!泥马写注释累死我了终于写完了!
  87. {
  88. ans = (ans + dp[M][i])%mod;
  89. }
  90. printf("%d\n",ans);
  91. }
  92. }

 

 

 

 

棋盘放子

有一个N*M(N<=5,M<=1000)的棋盘,现在有1*2及2*1的小木块无数个,要盖满整个棋盘,有多少种方式?答案只需要mod1,000,000,007即可。

例如:对于一个2*2的棋盘,有两种方法,一种是使用2个1*2的,一种是使用2个2*1的。

由于行数n很小,那么我们每一列的状态最多只有 1<<5 种,做法就是针对每一列,搜索出所有合法状态。

当前列仅仅影响下一列,比如说第一列放了1*2 ,那么第二列该位置就不能再放。

状态转移方程  dp[ j+1 ][ nex ] += dp[ j ][ state ] , nex是由当前state限制下能构成的合法状态

 

代码:

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<algorithm>
  4. #include<iostream>
  5. #include<string>
  6. #include<vector>
  7. #include<stack>
  8. #include<bitset>
  9. #include<cstdlib>
  10. #include<cmath>
  11. #include<set>
  12. #include<list>
  13. #include<deque>
  14. #include<map>
  15. #include<queue>
  16. using namespace std;
  17. typedef long long ll;
  18. const double PI = acos(-1.0);
  19. const double eps = 1e-6;
  20. const int INF = 0x3f3f3f3f;
  21. const int maxn = 100;
  22. const int mod = 1e9 + 7;
  23. int T, n, m;
  24. ll dp[11][(1 << 11) + 5];
  25. void dfs(int i,int j,int state, int nex)
  26. // 第几行,第几列,当前状态,对下一行的影响
  27. {
  28. if(i == n) // 到达第n+1 行的时候,当前列已经得到一种状态 state,且产生对下一行允许的 nex
  29. {
  30. dp[j+1][nex] += dp[j][state];
  31. return;
  32. }
  33. if( (1<<(i) &state) > 0) // 当前行 被占用了,那么就跳到下一行
  34. dfs(i+1,j,state,nex);
  35. else
  36. {
  37. dfs(i+1,j,state, nex|(1<<i));//在当前行摆上 1*2,则下一列被占用一个
  38. if(i+1<n && (((1<<(i+1)) & state) == 0) ) // 在当前列摆上2*1
  39. {
  40. dfs(i+2,j,state,nex);
  41. }
  42. }
  43. }
  44. int main()
  45. {
  46. while(~scanf("%d%d",&n,&m))
  47. {
  48. if(m == 0 && n == 0) break;
  49. memset(dp,0,sizeof(dp));
  50. dp[1][0] = 1;
  51. for(int j=1; j<=m; j++)
  52. {
  53. // 第j列
  54. for(int k=0; k<(1<<n); k++)
  55. // 各个状态
  56. {
  57. if(dp[j][k])//dp[j][k]!=0 表示第j行 k状态下,有方案可以放置木板,
  58. dfs(0,j,k,0);
  59. }
  60. }
  61. printf("%lld\n",dp[m+1][0]);
  62. }// 要求铺满,那么m+1 不能有露出来的}
  63. return 0;
  64. }

 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/blog/article/detail/50883
推荐阅读
相关标签
  

闽ICP备14008679号