状压DP学习笔记
有的时候,我们会发现一些问题的状态很难直接用几个数表示,这个时候我们就会用到状压dp啦~~。
状压就是状态压缩,就是讲原本复杂难以描述的状态用一个数或者几个数来表示qwq。状态压缩是一个很常用的技巧,把它运用到动态规划中有时候可以方便节省空间和时间,精简状态,方便状态转移。
找状态依然是状压dp的核心qwq。
多数状压dp都是将一个n维,每一维为0或1的状态压缩为一个2n的二进制数,用这个数二进制表示下每一位的值来表示这个状态qwq。(比如说储存一行:011110,每一个数字都表示其对应位置的合法性)
学好状压DP首先是需要熟练地掌握位运算的qwq,所以这里先介绍几个比较常用的位运算操作:
- 0&0=0; 0&1=0; 1&1=1;(与运算): &
- 0$|\(0=0; 0\)|\(1=1; 1\)|$1=1; (或运算): \(|\)
- 0^0=0; 0^1=1; 11=0;(异或运算): 
- 去掉最后一位(相当于/2): \(x>>1\)
- 在最后一位加零(相当于$*$2): \(x<<1\)
- 把最后一位变成1: \(x|1\)
- 把最后一位变成0: \(x|1-1\)
- 最后一位取反: \(x\)^\(1\)
除此之外还有几个常用的:
判断一个数字x二进制下第i位是不是等于1:(也可以理解成判断第i个点在不在集合中)
if(((1<<(i-1))&x)>0)
(就是将1左移i-1位之后,你会发现就只有第i位上面有1,其他位都是0,这时候再和x与,如果是1就是符合条件,0就是不符合)将一个数字x二进制下第i位更改成1:
x=x|(1<<(i-1))
{大概和上面是一样的,就是执行|操作)把一个数字二进制下最靠右的第一个1去掉:
x=x&(x-1)枚举s的子集:
for(int i=s;i;i=(i-1)&s){}若s是u的子集,那么s对于u的补集v:
v=s^u
在这里先贴上来自 OI Wiki 的状压DP常用格式:
int maxn=1<<n; //规定状态的上界
for (int i=0;i<maxn;i++){
if (i&(i<<1)) continue;//如果i情况不成立就忽略
Type[++top]=i;//记录情况i到Type数组中
}
for (int i=1;i<=top;i++){
if (fit(situation[1],Type[i]))
dp[1][Type[i]]=1;//初始化第一层
}
for (int i=2;i<=层数(dp上界);i++){
for (int l=1;l<=top;l++)//穷举本层情况
for (int j=1;j<=top;j++)//穷举上一层情况(上一层对本层有影响时)
if (situation[i],Type[l]和Type[j]符合题意)
dp[i][l]=dp[i][l]+dp[i-1][j];//改变当前层(i)的状态(l)的方案种数
}
for (int i=1;i<=top;i++) ans+=dp[上界][Type[i]];
通过上述代码我们可以了解到基本的状压DP思想和基础操作。
一般来讲,状压DP处理的数据范围很小,但是一般比爆搜的范围稍微大一些,就是在两位数的范围内。
下面来以几道典型例题来进一步理解状压DP:
[USACO06NOV]玉米田Corn Fields
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 100000000
using namespace std;
int n,m,ans;
int f[13][5000],cnt[13],a[13][13],done[5000],maxx;
//valid是该地方是否合法,maxx是最大状态数(上限)
int main(){
scanf("%d%d",&m,&n);
maxx=1<<n;
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
//读取每块土地的状态
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
cnt[i]=(cnt[i]<<1)+a[i][j];
//cnt[i]表示的是到第i行的状态
//因为我们是用二进制储存的状态(状压DP思想)
//所以要把该行的状态整理起来
for(int i=0;i<maxx;i++)
if(!(i&(i<<1)))
state[i]=1;
//我们现在要处理每个状态的合法性
//因为题目中提到没有两块相邻的土地,所以就是两个1不能相邻,我们通过左移一位并与运算来判断
f[0][0]=1;
for(int i=1;i<=m;i++)
for(int j=0;j<maxx;j++)
if(done[j]&&((j&cnt[i])==j))
//这里要判断土地是否贫瘠,还是用到位运算的原理
for(int k=0;k<maxx;k++)
//寻找上一行的合法情况
if((k&j)==0)
//该行有选择土地的同列上一行不能再选择,所以直接与就可以判断合法情况了
f[i][j]=(f[i][j]+f[i-1][k])%mod;
//如果合法要相加答案
for(int i=0;i<maxx;i++)
ans=(ans+f[m][i])%mod;
printf("%d\n",ans);
return 0;
}
上面我们是把所有的情况进行了枚举,然后判断如果不合法的话就跳过,如果合法才进行累计计算qwq,这样的话常数不是特别优秀。
下面这道题进行了常数优化。
通过预处理,可以处理出合法情况,然后将其储存起来。
互不侵犯
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 2000
using namespace std;
int n,m,cnt,MAX;
long long sum[20][MAXN][100];
int valid[MAXN],num[MAXN];
int main()
{
long long ans=0;
scanf("%d%d",&n,&m);
MAX=(1<<n)-1;
for(int i=0;i<=MAX;i++)
if (!(i&(i<<1)))
{
valid[++cnt]=i;
int cur_ans=0,x=i;
while(x)
{
cur_ans+=(x&1);
x>>=1;
}
num[cnt]=cur_ans;
sum[1][cnt][num[cnt]]=1;
}
for (int i=2;i<=n;i++)
for (int j=1;j<=cnt;j++)
for (int k=1;k<=cnt;k++)
{
if ((valid[j]&valid[k])||(valid[j]&(valid[k]<<1))||(valid[j]&(valid[k]>>1))) continue;
for (int l=0;l<=m;l++) sum[i][j][num[j]+l]+=sum[i-1][k][l];
}
for(int i=1;i<=cnt;i++) ans+=sum[n][i][m];
printf("%lld\n",ans);
}
宝藏
题解:https://www.cnblogs.com/fengxunling/p/9777606.html
状压DP学习笔记的更多相关文章
- 状压dp学习笔记(紫例题集)
P3451旅游景点 Tourist Attractions 这个代码其实不算是正规题解的(因为我蒟蒻)是在我们的hzoj上内存限制324MIB情况下过掉的,而且经过研究感觉不太能用滚动数组,所以那这个 ...
- MMM 状压dp学习记
状压dp学习记 by scmmm 开始日期 2019/7/17 前言 状压dp感觉很好理解(本质接近于爆搜但是又有广搜的感觉),综合了dp的高效性(至少比dfs,bfs优),又能解决普通dp难搞定的问 ...
- 状压DP复习笔记
前言 复习笔记第4篇.CSP RP++. 引用部分为总结性内容. 0--P1433 吃奶酪 题目链接 luogu 题意 房间里放着 \(n\) 块奶酪,要把它们都吃掉,问至少要跑多少距离?一开始在 \ ...
- 状压dp(状态压缩&&dp结合)学习笔记(持续更新)
嗯,作为一只蒟蒻,今天再次学习了状压dp(学习借鉴的博客) 但是,依旧懵逼·································· 这篇学习笔记是我个人对于状压dp的理解,如果有什么不对的 ...
- [学习笔记]状压dp
状压 \(dp\) 1.[SDOI2009]Bill的挑战 \(f[i][j]\) 表示匹配到字符串的第 \(i\) 位状态为 \(j\) 的方案数 那么方程就很明显了,每次枚举第 \(i\) 位的字 ...
- 算法笔记-状压dp
状压dp 就是把状态压缩的dp 这样还是一种暴力但相对于纯暴力还是优雅的多. 实际上dp就是经过优化的暴力罢了 首先要了解位运算 给个链接吧 [https://blog.csdn.net/u01337 ...
- 「算法笔记」状压 DP
一.关于状压 dp 为了规避不确定性,我们将需要枚举的东西放入状态.当不确定性太多的时候,我们就需要将它们压进较少的维数内. 常见的状态: 天生二进制(开关.选与不选.是否出现--) 爆搜出状态,给它 ...
- CH0103最短Hamilton路径 & poj2288 Islands and Brigdes【状压DP】
虐狗宝典学习笔记: 取出整数\(n\)在二进制表示下的第\(k\)位 \((n >> ...
- 有关状压DP
[以下内容仅为本人在学习中的所感所想,本人水平有限目前尚处学习阶段,如有错误及不妥之处还请各位大佬指正,请谅解,谢谢!] 引言 动态规划虽然已经是对暴力算法的优化,但在某些比较特别的情况下,可以通过一 ...
随机推荐
- hibernate 错误 could not determine type for
今天配置实体类注解时,出现以下错误: org.hibernate.MappingException: Could not determine type for: com.oneToOne.IdCard ...
- ios常用空间UIScrollViewIndicator的一些属性
UIScrollView属性: 1 alwaysBounceHorizontal BOOL值,当水平滚条到达终点,总是(视图)弹跳 2 alwaysBounceVertical ...
- 使用OpenSsl自己CA根证书,二级根证书和颁发证书(亲测步骤)
---恢复内容开始--- 一.介绍 企业自用, 到证书机构签发证书的费用和时间等都可以省下..... SSl证书的背景功用.......(省略万字,不废话) 可以参考: SSL证书_百度百科 X509 ...
- bash's parameter expansion
[bash's parameter expansion] #: find first from left, remove-left ##: find last from left, remove le ...
- Sql Server 2008R2 数据库发布与订阅
背景描述: 发布服务器A: (远程端) , 数据库服务名: GUANWANG1 订阅服务器B: (本机) , 数据库服务名: PC-LLRDBA 需要从服务器A中数据库发布,然后在B中订阅A发布 ...
- 批处理for命令详解(转)
FOR这条命令基本上都被用来处理文本,但还有其他一些好用的功能! 看看他的基本格式(这里我引用的是批处理中的格式,直接在命令行只需要一个%号)FOR 参数 %%变量名 IN (相关文件或命令) DO ...
- C#中一个窗口是一个类呢,还是一个窗口类的实例呢?(转)
C#中一个窗口是一个类呢,还是一个窗口类的实例呢? 答: 没有一个人说到重点上. 一个窗口,它不是仅仅用一个类可以描述的: 首先,这个窗口的数据类型类型,是从Form类派生下来的,也就是说它的定义是一 ...
- es学习-基础增删改查
创建库 插入数据 修改文档: 查询文档: 删除文档:
- SQL Server数据库大型应用解决方案总结(转)
出处:http://tech.it168.com/a2012/0110/1300/000001300144.shtml [IT168 技术]随着互联网应用的广泛普及,海量数据的存储和访问成为了系统设计 ...
- 解决Emoji存储MySQL报错问题
在解决之前,得先说明一下为什么会出现报错,Emoji表情占用4个字节,但是MySQL数据库UTF-8编码最多只能存储3个字节,就会导致存储不进去 如何解决Emoji存储问题 mysql 的 utf8编 ...