状压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 具体思路: 我们可以先把所有合法的情况枚举出来,然后对第一行判断有多少种情况满足,然后对于剩下的行数 ...
随机推荐
- display 的 32 种写法
从大的分类来讲, display的 32种写法可以分为 6个大类,再加上 1个全局类,一共是 7大类: 外部值 内部值 列表值 属性值 显示值 混合值 全局值 外部值 所谓外部值,就是说这些值只会直接 ...
- bzoj 1188 [HNOI2007]分裂游戏 SG函数 SG定理
[HNOI2007]分裂游戏 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 1394 Solved: 847[Submit][Status][Dis ...
- jsp中的开头的作用
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1&q ...
- Windows下Nginx的启动、停止等基本命令
在Windows下使用Nginx,我们需要掌握一些基本的操作命令,比如:启动.停止Nginx服务,重新载入Nginx等,下面我就进行一些简单的介绍. 1.启动: C:\server\nginx-1.0 ...
- Codeforces785E - Anton and Permutation
Portal Description 对一个长度为\(n(n\leq2\times10^5)\)的数列\(a\)进行\(m(m\leq5\times10^4)\)次操作,数列初始时为\(\{1,2,. ...
- Docker系统五:Docker仓库
创建Docker Hub账户 登录和上传镜像到Hub.docker.com docker login //登陆hub.docker.com docker tag ubutun1404-baseimag ...
- Java兔子问题
题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? /** * @Title:Rabbit.java ...
- Nginx HTTP模块指令
alias 指令 该指令用于在url和系统路径之间的映射. location /a/{ alias /b/; } error_page 定义错误页面 error_page 404 /404.html; ...
- STM32F4 串口实验中收不到超级终端发送的数据,调试工具却可以
我用串口精灵发送数据没有问题,但是接收数据没反应. 串口接受的时候必须要用中断的,你发送只靠单一的标志位是可以判断的,但是接受的时候,你是一直停留在while里面,我们判断接受是否完成,通过检测是否收 ...
- Java报SQLException
Java报SQLException 具体错误如下: java.sql.SQLException:Column count doesn't match value count at row 1