状压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 具体思路: 我们可以先把所有合法的情况枚举出来,然后对第一行判断有多少种情况满足,然后对于剩下的行数 ...
随机推荐
- 基于Java SE集合的充值管理系统
1.功能分析 ①管理员管理 注册.登录.退出 ②注册一卡通:记录相应信息. ③充值管理:对一卡通账户进行充值,查询,修改. 2.技术要求 ①Java 基础知识 + 集合类(模拟数据库). ②数据用对象 ...
- Shiro报错-[org.apache.shiro.mgt.AbstractRememberMeManager] - There was a failure while trying to retrieve remembered principals.
2017-04-08 11:55:33,010 WARN [org.apache.shiro.mgt.AbstractRememberMeManager] - There was a failure ...
- 剑指offer第三天
21.栈的压入.弹出序列 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3, ...
- jsp去除空行的web.xml配置
在jsp中我们引入的标签,例如jstl的标签,循环遍历等等,可能会产生很多空行,其实也没什么,不会影响展示,但是空行多多少少会影响性能,这是我们只需要在web.xml中配置一下我们就可以很简单的去掉, ...
- iOS.Animations.by.Tutorials.v2.0汉化(三)
第2章:Springs 在前一章中,您学习了如何创建UIKit的基本动画,包括如何提供起始值和结束值随着时间的UIKit,自动为你创建一个动画. 到目前为止,你的动画一直是单方向的流体运动.当你激活一 ...
- 实战项目:EMOS集成邮件平台
实战项目:EMOS集成邮件平台用户邮箱系统:http://mailAnonymous.cn/邮件服务器管理平台http://mailAnonymous.cn/extman 项目需求:随着公司规模不断扩 ...
- 对HI3531的GPIO使用的再分析
在一个嵌入式系统中使用最多的莫过于 通用输入输出 GPIO口.看到论坛中经常有朋友问海思为什么没有提供GPIO驱动.其实不然. 在海思SDK xxx/osdrv/tools/board_tools/ ...
- Java之Frame
Java之Frame 1.测试Java中的Frame 具体实现如下: /** * @Title:FrameJava.java * @Package:com.you.model * @Descripti ...
- Looks like the Spring listener was not configured for your web app!
1.错误描述 usage: java org.apache.catalina.startup.Catalina [ -config {pathname} ] [ -nonaming ] { -help ...
- 安装使用pyclone进行克隆演化推断
pyclone介绍 可以根据多个样品突变的allele frequency 和 copy number,推断出有该突变的细胞克隆所占的比例(cellular prevalence)在不同样品间的变 ...