状压dp的含义

  在我们解决动态规划题目的时候,dp数组最重要的一维就是保存状态信息,但是有些题目它的具有dp的特性,并且状态较多,如果直接保存的可能需要三维甚至多维数组,这样在题目允许的内存下势必是开不下的,那么我们能不能想个办法,把它压缩成一维呢?对,二进制.一般的动规题目数据范围都不会太大,那么就可以把几个状态全部压缩成一个二进制数保存下来,这样就大大节省了空间,来允许我们进行其他的操作,这就叫做状态压缩.运用状态压缩来保存状态的dp就叫做状压dp,这类dp一般数据范围有一项很小(好像是不超过16吧),看到这种数据范围就可以往状压上想

纸上谈兵是没用的,下面我们来看一道例题

POJ3254

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

解题思路:按照刚才我说的,题目中的m,n最大都只有12,我们要很快想到状压dp,那么如何状压呢?其实状压dp就是一种枚举,是最暴力的一种dp.

在题目中,有1的地方就可以种植,否则不行,在不考虑时间复杂度的情况下,我们是不是会想到打暴搜,枚举每一种情况,如果一块地上已经种了草,那么上下左右就都不能种了.我们经这种思路转化成二进制,1代表在这块地上种植,0代表不种,例如:010就代表在第二块地种植,其他地都不种.

我们枚举每一行的状态,在左右不相邻的情况下,再判断下一行不和本行状态冲突的状态(如:第一行是0 1 0,第二行是0 1 0就冲突了,即上下行同一位置不能同时种植),这样我们只需要预处理出第一行的状态就可以递推出其他行的所有满足条件的状态个数了

下面来分析一下题目样例

1 1 1

0 1 0

第一行满足条件的状态有

1 0 0 0
2 1 0 0 
3 0 1 0
4 0 0 1
5 1 0 1

第二行满足条件的状态有

1 0 0 0
2 0 1 0

根据乘法原理有5*2=10种方法,但其中一种第一行0 1 0和第二行0 1 0是冲突的,所以结果为10-1=9种方案

设计dp数组的状态,状压dp状态应该还是比较好设计的,本题为dp[i][state[j]]表示到第i行到第j种状态满足条件的方案数

满足无后效性原则,下一行的状态只能由前一行转移过来

dp[i+1][state[j]]+=dp[i][state[k]] state[k]表示第i行满足条件的状态

总结一下思路:先枚举第一行,把所有可能的状态和第一行的题目所给环境对比,如果成功,则在循环里继续枚举第二行,把所有可能的状态和第二行的环境对比,如果成功,再和第一行填入的状态对比,如果又匹配成功,则dp[2][000] = dp[2][000] + dp[1][100];方法数加到第二行。这就是一次循环结束了,重新枚举第二行...

     //cur[i]表示第i行的环境
for(int i=;i<=m;i++) {
for(int j=;j<=n;j++) {
int a; in(a);//输入环境
if(!a) cur[i]|=(<<(n-j));//这个有两点要注意,一个是所有的0变成1,1变成0(这个必须),一个是反向(正向也可以)存环境------>cur[i]|=(1<<(j-1));
}
}

我们假设一下如果不是0,1互换,那么我们后面判断它是否合法时就会出现问题,比如我第1行的状态为1 0 1,互换后为0 1 0,在后面的程序中有这样一条判断是否合法的语句

if((can[i] & cur[j])==0) 代表它合法--------互换后的程序

不互换的话就是这样 if(can[i] & cur[j]) 乍一看好像没什么不对,但是我们考虑一种情况,就是当我们枚举的状态为0时,后面的这一种语句是无法满足要求的,但在题目中不种植也算一种方案,所以我们就需要0,1互换这个操作

   for(int i=;i<tot;i++) if(!(i&(i<<))) can[++cnt]=i;//所有左右两边不相邻的状态

这是保存状态的语句,tot=1<<n,n为列数.题目要求相邻两边不能同时种植,我们就把一个状态,左移一位也就是取它的下一位,再与它自己想与,若大于0,则代表有相邻的1,否则就没有.

这样就巧妙的判断了左右相邻的情况

  for(int i=;i<=cnt;i++)if(!(cur[]&can[i])) dp[][can[i]]=;//预处理第1行的可行状态

只要预处理第1行就好了,后面的行数都是由它转移而来的对吧

 for(int i=;i<m;i++) //枚举1~m-1行
for(int j=;j<=cnt;j++)//枚举所有可行的状态
if((cur[i]&can[j])==)//如果第i行满足环境要求
for(int k=;k<=cnt;k++)//枚举第i+1行的状态
if(((can[k]&cur[i+])==) && ((can[j]&can[k])==))//和第i+1行的状态满足第i+1行的环境以及不与的第i行状态冲突
dp[i+][can[k]]=(dp[i+][can[k]]+(dp[i][can[j]]%mod))%mod;//状态数相加

这就是本代码的核心程序,处理出每一行满足条件的方案数.

最后贴一下总代码

 #include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdio>
#include<string>
#define in(i) (i=read())
using namespace std;
const int mod=;
int read()
{
int ans=,f=; char i=getchar();
while(i<''||i>'') {if(i=='-') f=-; i=getchar();}
while(i>=''&&i<=''){ans=(ans<<)+(ans<<)+i-''; i=getchar();}
return ans*f;
}
int dp[][<<],can[<<],cur[];
int main()
{
int m,n,cnt=,ans=,tot;
in(m);in(n); tot=<<n;
//cur[i]表示第i行的环境
for(int i=;i<=m;i++) {
for(int j=;j<=n;j++) {
int a; in(a);//输入环境
if(!a) cur[i]|=(<<(n-j));//这个有两点要注意,一个是所有的0变成1,1变成0(这个必须),一个是反向(正向也可以)存环境------>cur[i]|=(1<<(j-1));
}
}
for(int i=;i<tot;i++) if(!(i&(i<<))) can[++cnt]=i;//所有左右两边不相邻的状态
for(int i=;i<=cnt;i++)if(!(cur[]&can[i])) dp[][can[i]]=;//预处理第1行的可行状态
for(int i=;i<m;i++)//枚举1~m-1行
for(int j=;j<=cnt;j++)//枚举所有可行的状态
if((cur[i]&can[j])==)//如果第i行满足环境要求
for(int k=;k<=cnt;k++)//枚举第i+1行的状态
if(((can[k]&cur[i+])==) && ((can[j]&can[k])==))//和第i+1行的状态满足第i+1行的环境以及不与的第i行状态冲突
dp[i+][can[k]]=(dp[i+][can[k]]+(dp[i][can[j]]%mod))%mod;//状态数相加
for(int i=;i<=cnt;i++)
ans=(ans+dp[m][can[i]])%mod;
cout<<ans<<endl;
return ;
}

状压dp的其他例题

1.SCOI2005互不侵犯  题解

2.Codefoces--Kefa and Dishes 题解

3.POJ3311旅行商问题 题解

4.NOIP2016愤怒的小鸟 题解

5.SCOI2008奖励关 题解

状态压缩十分有用,并不一定只能用于dp,有些范围比较大的数据结构有时也需要状压,留待同学们以后做题时自己去发现

状压dp入门的更多相关文章

  1. poj3254状压DP入门

    G - 状压dp Crawling in process... Crawling failed Time Limit:2000MS     Memory Limit:65536KB     64bit ...

  2. poj2686 状压dp入门

    状压dp第一题:很多东西没看懂,慢慢来,状压dp主要运用了位运算,二进制处理 集合{0,1,2,3,....,n-1}的子集可以用下面的方法编码成整数 像这样,一些集合运算就可以用如下的方法来操作: ...

  3. 状压DP入门详解+题目推荐

    在动态规划的题型中,一般叫什么DP就是怎么DP,状压DP也不例外 所谓状态压缩,一般是通过用01串表示状态,充分利用二进制数的特性,简化计算难度.举个例子,在棋盘上摆放棋子的题目中,我们可以用1表示当 ...

  4. POJ:1185-炮兵阵地(状压dp入门)

    炮兵阵地 Time Limit: 2000MS Memory Limit: 65536K Description 司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队.一个N*M的地图由N行M列组 ...

  5. POJ 3254 & POJ 1185(状压DP入门)

    Corn Fields Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 16773   Accepted: 8860 Desc ...

  6. poj 3254 状压dp入门题

    1.poj 3254  Corn Fields    状态压缩dp入门题 2.总结:二进制实在巧妙,以前从来没想过可以这样用. 题意:n行m列,1表示肥沃,0表示贫瘠,把牛放在肥沃处,要求所有牛不能相 ...

  7. 状压dp入门第一题 poj3254

    题目链接 http://poj.org/problem?id=3254 转自http://blog.csdn.net/harrypoirot/article/details/23163485 #inc ...

  8. 洛谷 P1879 玉米田(状压DP入门题)

    传送门 https://www.cnblogs.com/violet-acmer/p/9852294.html 题解: 相关变量解释: int M,N; int plant[maxn][maxn];/ ...

  9. P1879 [USACO06NOV]玉米田Corn Fields (状压dp入门)

    题目链接: https://www.luogu.org/problemnew/show/P1879 具体思路: 我们可以先把所有合法的情况枚举出来,然后对第一行判断有多少种情况满足,然后对于剩下的行数 ...

随机推荐

  1. js中的各种“位置”——“top、clientTop、scrollTop、offsetTop……”,你知道多少

    当要做一些与位置相关的插件或效果的时候,像top.clientTop.scrollTop.offsetTop.scrollHeight.clientHeight.offsetParent...看到这么 ...

  2. 利用mock提高效率

    利用mock提高效率 谈到mock,就不得不讲前后端分离.理想情况下前后端不分离,由全栈的人以product和infrastructure的维度进行开发,效率是最高的.近些年来业务的复杂度越来越高,真 ...

  3. iOS实现从服务器请求json数据并转化成NSDictionary

    NSURL *url = [NSURL URLWithString:URL]; NSURLRequest *request = [NSURLRequest requestWithURL:url cac ...

  4. POJ - 1417 并查集+背包

    思路:很简单的种类并查集,利用并查集可以将所有的人分成几个集合,每个集合又分为好人和坏人集合,直接进行背包dp判断有多少种方法可以在取了所有集合并且人数正好凑足p1个好人的方案.dp(i, j)表示前 ...

  5. freemaker中的map遍历

    前两天在freemaker中遇到一个问题,怎么遍历一个Map<List<Object>的map呢? 网上找个很多都是类似下面的: <#if map?exists> < ...

  6. 对于JAVA程序优化的一些想法,读书有感.治疗强迫症良药

    在深入了解Java虚拟机里读到:在try{}块里面执行代码,比if(x!=null)效率要高,前提是被catch的几率很低的情况下. 但是 在Effective Java里读到:因为异常机制的设计初衷 ...

  7. 好用的Markdown编辑器汇总

    Markdown 是一种简单的.轻量级的标记语法.用户可以使用诸如 * # 等简单的标记符号以最小的输入代价生成极富表现力的文档. Markdown具有很多优点: 写作中添加简单符号即完成排版,所见即 ...

  8. Android常见Crash类型分析(一)

    问题1.   java.lang.IllegalStateException: The specified child already has a parent. You must call remo ...

  9. VBR与CBR的区别是什么?

    VBR是动态码率.CBR是静态码率. VBR(Variable Bitrate)动态比特率.也就是没有固定的比特率,压缩软件在压缩时根据音频数据即时确定使用什么比特率,这是以质量为前提兼顾文件大小的方 ...

  10. Windows2003查看远程桌面连接的用户

    要查看通过远程连接windows2003的用户,则打开任务管理器,切换到“用户”选项卡上进行查看.