状压dp入门
状压dp的含义
在我们解决动态规划题目的时候,dp数组最重要的一维就是保存状态信息,但是有些题目它的具有dp的特性,并且状态较多,如果直接保存的可能需要三维甚至多维数组,这样在题目允许的内存下势必是开不下的,那么我们能不能想个办法,把它压缩成一维呢?对,二进制.一般的动规题目数据范围都不会太大,那么就可以把几个状态全部压缩成一个二进制数保存下来,这样就大大节省了空间,来允许我们进行其他的操作,这就叫做状态压缩.运用状态压缩来保存状态的dp就叫做状压dp,这类dp一般数据范围有一项很小(好像是不超过16吧),看到这种数据范围就可以往状压上想
纸上谈兵是没用的,下面我们来看一道例题
题目大意:农夫有一块地,被划分为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的其他例题
2.Codefoces--Kefa and Dishes 题解
状态压缩十分有用,并不一定只能用于dp,有些范围比较大的数据结构有时也需要状压,留待同学们以后做题时自己去发现
状压dp入门的更多相关文章
- poj3254状压DP入门
G - 状压dp Crawling in process... Crawling failed Time Limit:2000MS Memory Limit:65536KB 64bit ...
- poj2686 状压dp入门
状压dp第一题:很多东西没看懂,慢慢来,状压dp主要运用了位运算,二进制处理 集合{0,1,2,3,....,n-1}的子集可以用下面的方法编码成整数 像这样,一些集合运算就可以用如下的方法来操作: ...
- 状压DP入门详解+题目推荐
在动态规划的题型中,一般叫什么DP就是怎么DP,状压DP也不例外 所谓状态压缩,一般是通过用01串表示状态,充分利用二进制数的特性,简化计算难度.举个例子,在棋盘上摆放棋子的题目中,我们可以用1表示当 ...
- POJ:1185-炮兵阵地(状压dp入门)
炮兵阵地 Time Limit: 2000MS Memory Limit: 65536K Description 司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队.一个N*M的地图由N行M列组 ...
- POJ 3254 & POJ 1185(状压DP入门)
Corn Fields Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 16773 Accepted: 8860 Desc ...
- poj 3254 状压dp入门题
1.poj 3254 Corn Fields 状态压缩dp入门题 2.总结:二进制实在巧妙,以前从来没想过可以这样用. 题意:n行m列,1表示肥沃,0表示贫瘠,把牛放在肥沃处,要求所有牛不能相 ...
- 状压dp入门第一题 poj3254
题目链接 http://poj.org/problem?id=3254 转自http://blog.csdn.net/harrypoirot/article/details/23163485 #inc ...
- 洛谷 P1879 玉米田(状压DP入门题)
传送门 https://www.cnblogs.com/violet-acmer/p/9852294.html 题解: 相关变量解释: int M,N; int plant[maxn][maxn];/ ...
- P1879 [USACO06NOV]玉米田Corn Fields (状压dp入门)
题目链接: https://www.luogu.org/problemnew/show/P1879 具体思路: 我们可以先把所有合法的情况枚举出来,然后对第一行判断有多少种情况满足,然后对于剩下的行数 ...
随机推荐
- jdbc 报错解决办法
刚刚看到一个童鞋出现了这个问题 其实这个问题很好解决 在工程中创建一个lib目录: 然后讲mysql包复制进去 然后对着包点击右键 build path就可以了 最后面再次运行就可以了 jar包地址下 ...
- nxlog4go Log Levels and Pattern Layout
Log levels nxlog4go provides log levels as below: type Level int const ( FINEST Level = iota FINE DE ...
- Nginx拦截算法
Nginx流量拦截算法 nginx 夏日小草 2015年10月22日发布 | 1 收藏 | 40 4.2k 次浏览 0x00.About 电商平台营销时候,经常会碰到的大流量问题,除了做流量分 ...
- ARC068E - Snuke Line
原题链接 题意简述 给出个区间和.求对于任意,有多少个区间包含的倍数. 题解 考虑怎样的区间不包含的倍数. 对于的倍数和,满足的区间不包含任何的倍数. 于是转化为二维数点问题,可以用可持久化线段树解决 ...
- 记录一个 spring cloud 配置中心的坑,命令行端口参数无效,被覆盖,编码集问题无法读取文件等.
spring cloud 配置中心 结合GIT , 可以运行时更新配置文件.发送指令让应用重新读取配置文件. 最近在测试服务器实现了一套,结果CPU 实用率暴增,使用docker compose启动 ...
- hive:join操作
hive的多表连接,都会转换成多个MR job,每一个MR job在hive中均称为Join阶段.按照join程序最后一个表应该尽量是大表,因为join前一阶段生成的数据会存在于Reducer 的bu ...
- linux RHCS集群 高可用web服务器
RHCS集群,高可用服务器 高可用 红帽集群套件,提供高可用性,高可靠性,负载均衡,快速的从一个节点切换到另一个节点(最多16个节点)负载均衡 通过lvs提供负载均衡,lvs将负载通过负载分配策略,将 ...
- android WebP解析开源库-支持高清无损
在我们的项目中需要支持WebP高清无损图片,推荐一个我们已经使用的解析开源库给大家:https://github.com/keshuangjie/WebpExample/tree/master/lib ...
- dojo表格分页之各个参数代表的意义(一)
下面是dojo表格分页参数代表的意义 //每页可以显示10/15/20/25/30条记录 (1)pageSizes: [10, 15, 20, 25,30], //每页显示的记录从多少到多少,共多少条 ...
- HighCharts之2D柱状图、折线图的组合多轴图
HighCharts之2D柱状图.折线图的组合多轴图 1.实例源码 SomeAxis.html: <!DOCTYPE html> <html> <head> < ...