状态压缩DP

对于一个集合,他一有\(2^n\)个子集,而状态压缩就是枚举这些子集,每一个状态就是一个由\(01\)构成的集合,如果为\(0\)就表示不选当前的元素,否则就表示选。因为状态压缩将每一个状态压缩成了一个用二进制表示的数,所以不光可以节省空间,还可以节省时间。

因为是枚举子集,所以时间复杂度为\(O(2^n)\),一般使用的标志就是\(n\le20\).

关灯问题

这是一道经典的\(bfs\)加状态压缩的题目!!

思路

题目要求最多按多少次可以将所有的灯关闭。我们可以将题目给出的条件转化为一个图。每个节点所表示的是当前所有灯的开关情况,但是我们发现这样每个节点的信息要用一个一维数组表示,不好写代码,所以我们可以用状态压缩将每次的开关灯状态转化为一个二进制表示,接着跑一遍\(bfs\)。

对于每种状态,枚举接下来按的按钮,生成新一个的状态,如果当前状态没有被访问过,就加进队列,并记录步数,因为是\(bfs\),所以此步数一定是到达当前状态的最小步数。

位运算

题目中涉及到第\(i\)个开关对\(j\)盏灯的影响,设当前状态为\(v\)

1.如果\(a[i][j]=1\)并且第\(j\)盏灯是开着的

\[v=v\oplus(1<<(j-1))
\]

2.如果\(a[i][j]=0\),不用管

3.如果\(a[i][j]=0\)并且第\(j\)盏灯是关着的

\[v=v\oplus(1<<(j-1))
\]

\(code\)

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[110][11],d[1<<11];
queue<int>q;
bool vis[1<<11];
void bfs(){
q.push((1<<n)-1);
vis[(1<<n)-1]=1;
d[(1<<n)-1]=0;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=1;i<=m;i++){
int v=u;
for(int j=1;j<=n;j++){
if(a[i][j]&&(v&(1<<(j-1))))v=(v^(1<<(j-1)));
else if(a[i][j]==-1&&(!(v&(1<<(j-1)))))v=(v^(1<<(j-1)));
}
if(!vis[v]){
vis[v]=1;
q.push(v);
d[v]=d[u]+1;
if(v==0)return;
} }
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
bfs();
if(d[0])cout<<d[0];
else cout<<-1;
return 0;
}

炮兵阵地


这是一道状压DP的典题!!

题目大意

每一个炮兵部队都有一定的攻击范围,题目要求在地图上最多能安排多少个炮兵部队,而且要满足以下几个条件:

1.各个部队之间不能互相伤害

2.部队不能安排在山坡上

思路梳理

读入

状压DP就是用一串\(0,1\)来表示当前状态,读入时要将每一行的字符转化为二进制,每次读入一个字符,如果为\(H\)那么二进制串的当前位是\(1\),表示山坡,否则是\(0\)表示平地。

for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
char x;
cin>>x;
a[i]<<=1;
if(x=='H')a[i]+=1;
}
}

预处理

为了方便统计每种状态下能安排的炮兵部队的个数,即\(1\)的个数,所以需要预处理,每个\(01\)串中\(1\)的个数

int getsum(int S){
int tot=0;
while(S){
if(S&1)++tot;
S>>=1;
}
return tot;
}

动态规划方程

因为一个炮兵部队的攻击范围涉及当前行状态\(s\),上一行状态\(l\),上上行状态\(fl\),当前行数\(i\),显然设一个四位数组\(f[s][l][fl][i]\),空间复杂度为\(O(2^{30}*100)\),显然会炸空间,所以简化设一个三维数组,\(f[s][l][i]\)表示当前状态为\(s\),上一状态为\(l\),当前行数为\(i\)。经过反复推敲,动态规划方程

\[f[s][l][i]=max(f[s][l][i],f[l][fl][i-1]+sum[s])
\]

但是发现此时的空间复杂度\(O(2^{20}*100)\),约为\(10^8\),但是见过世面的我们发现动态规划方程只与前三维有关,所以开滚动数组好啦!

条件限制

1.部队只能安排在山坡上,则

\[a[i]\&S!=0
\]

因为如果结果为\(1\),则表示,当前地形为山坡,并且还安排了炮兵部队

2.部队间的左右距离必须大于\(2\)

\[s\&(s<<1)!=0,S\&(s<<2)!=0
\]

如果为\(1\),就表示存在一位\(i\),右边的\(i+1\)或\(i+2\),与他都为1,或者左边的\(i-1\)或\(i-2\),与他都为1

3.部队间的前后距离必须大于\(2\)

\[s\&fl!=0\&\&s\&l!=0
\]

理解类似于1

\(code\)

注意!!!状态的最大值为\(111...1\)即\(2^m-1\)所以代码中要写 $$<2^m$$

#include <bits/stdc++.h>
using namespace std;
int n,m,a[110];
int sum[1<<10],f[1<<10][1<<10][5];
int ans=0;
int getsum(int S){
int tot=0;
while(S){
if(S&1)++tot;
S>>=1;
}
return tot;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
char x;
cin>>x;
a[i]<<=1;
if(x=='H')a[i]+=1;
}
}
for(int i=0;i<(1<<m);i++){
sum[i]=getsum(i);
}
for(int S=0;S<(1<<m);S++){
if(!(S&a[1]||(S&(S<<1))||(S&(S<<2)))){
f[1][S][1]=sum[S];
}
}
for(int l=0;l<(1<<m);l++){
for(int s=0;s<(1<<m);s++){
if(!(l&s||(l&a[1])||(s&a[2])||(l&(l<<1))||(l&(l<<2))||(s&(s<<1))||(s&(s<<2))))
f[l][s][2]=sum[s]+sum[l];
}
}
for(int i=3;i<=n;i++){
for(int fl=0;fl<(1<<m);fl++){
if((fl&a[i-2])||(fl&(fl<<1))||(fl&(fl<<2)))continue;
for(int l=0;l<(1<<m);l++){
if(l&a[i-1]||(l&fl)||(l&(l<<1))||(l&(l<<2)))continue;
for(int s=0;s<(1<<m);s++){
if(s&l||(s&fl)||(s&a[i])||(s&(s<<1))||(s&(s<<2)))continue;
f[l][s][i%3]=max(f[l][s][i%3],f[fl][l][(i-1)%3]+sum[s]);
}
}
}
}
for(int l=0;l<(1<<m);l++){
for(int s=0;s<(1<<m);s++)
ans=max(ans,f[l][s][n%3]);
}
cout<<ans;
return 0;
}

吃奶酪

这也是一类状态压缩DP题目

思路

根据题意,必须经过每个节点有且只有一次,显然走过的路程是一条链,而不是一棵树,所以没法用最小生成树做!!

接下来考虑状态,每次吃一块奶酪,对总路程的贡献是与他上一次吃的奶酪所连边的长度,所以显然我们需要一维数组来记录他这一次吃的是哪块奶酪,同时我们还需要记录已经有哪些节点已经在这条链上,也就是哪些奶酪已经被吃过了,于是状态就出来了.\(f[i][j]\)表示当前吃的是第\(i\)块奶酪,已经吃的奶酪为\(j\)(这里用到了状态压缩)。

状态转移方程

转移方程是非常显然的,既然我们已经知道当前吃的是\(i\)块奶酪,吃过的奶酪为\(k\),那么我们就只用枚举上一次吃的\(j\)块奶酪,在加上\(i,j\)之间的边长\(d[i][j]\)就好了.

\[f[i][k]=min(f[i][k],f[j][k^(1<<(i-1))]+d[j][i])
\]

\(code\)

注意!!!! 在转移时因为\(i,j\)并不具有单调性,而\(k\)因为是将第\(i\)位从1变成\(0\),所以具有单调性,因此要将\(k\)放到循环的最外层

 #include<bits/stdc++.h>
#define maxn 16
using namespace std;
struct node{
double x,y;
}a[maxn];
int n;
double f[maxn][1<<16],d[maxn][maxn];
double calc(int x,int y){
double dx=a[x].x-a[y].x;
double dy=a[x].y-a[y].y;
return sqrt(dx*dx+dy*dy);
}
int main(){
cin>>n;
a[0].x=a[0].y=0;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].y;
d[0][i]=calc(0,i);
d[i][0]=d[0][i];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
d[i][j]=calc(i,j);
}
}
memset(f,127,sizeof(f));
double ans=f[0][0];
for(int i=1;i<=n;i++){
f[i][1<<(i-1)]=d[0][i];
}
for(int i=1;i<(1<<n);i++){
for(int j=1;j<=n;j++){
if(!(i&(1<<(j-1))))continue;
for(int k=1;k<=n;k++){
if(k==j)continue;
if(!(i&(1<<(k-1))))continue;
f[j][i]=min(f[j][i],f[k][i-(1<<(j-1))]+d[k][j]);
//cout<<f[j][i]<<endl;
}
}
}
// for(int i=1;i<=n;i++){
// for(int j=1;j<(1<<n);j++){
// cerr<<f[i][j]<<endl;
// }
// }
for(int i=1;i<=n;i++){
ans=min(ans,f[i][(1<<n)-1]);
}
printf("%.2lf",ans);
return 0;
}

【学习笔记】状压DP的更多相关文章

  1. [学习笔记]状压dp

    状压 \(dp\) 1.[SDOI2009]Bill的挑战 \(f[i][j]\) 表示匹配到字符串的第 \(i\) 位状态为 \(j\) 的方案数 那么方程就很明显了,每次枚举第 \(i\) 位的字 ...

  2. 算法笔记-状压dp

    状压dp 就是把状态压缩的dp 这样还是一种暴力但相对于纯暴力还是优雅的多. 实际上dp就是经过优化的暴力罢了 首先要了解位运算 给个链接吧 [https://blog.csdn.net/u01337 ...

  3. 状压dp(状态压缩&&dp结合)学习笔记(持续更新)

    嗯,作为一只蒟蒻,今天再次学习了状压dp(学习借鉴的博客) 但是,依旧懵逼·································· 这篇学习笔记是我个人对于状压dp的理解,如果有什么不对的 ...

  4. 状压dp学习笔记(紫例题集)

    P3451旅游景点 Tourist Attractions 这个代码其实不算是正规题解的(因为我蒟蒻)是在我们的hzoj上内存限制324MIB情况下过掉的,而且经过研究感觉不太能用滚动数组,所以那这个 ...

  5. 状压DP学习笔记

    有的时候,我们会发现一些问题的状态很难直接用几个数表示,这个时候我们就会用到状压dp啦~~. 状压就是状态压缩,就是讲原本复杂难以描述的状态用一个数或者几个数来表示qwq.状态压缩是一个很常用的技巧, ...

  6. MMM 状压dp学习记

    状压dp学习记 by scmmm 开始日期 2019/7/17 前言 状压dp感觉很好理解(本质接近于爆搜但是又有广搜的感觉),综合了dp的高效性(至少比dfs,bfs优),又能解决普通dp难搞定的问 ...

  7. 状压DP复习笔记

    前言 复习笔记第4篇.CSP RP++. 引用部分为总结性内容. 0--P1433 吃奶酪 题目链接 luogu 题意 房间里放着 \(n\) 块奶酪,要把它们都吃掉,问至少要跑多少距离?一开始在 \ ...

  8. 「算法笔记」状压 DP

    一.关于状压 dp 为了规避不确定性,我们将需要枚举的东西放入状态.当不确定性太多的时候,我们就需要将它们压进较少的维数内. 常见的状态: 天生二进制(开关.选与不选.是否出现--) 爆搜出状态,给它 ...

  9. CH0103最短Hamilton路径 & poj2288 Islands and Brigdes【状压DP】

    虐狗宝典学习笔记: 取出整数\(n\)在二进制表示下的第\(k\)位                                                    \((n >> ...

  10. 有关状压DP

    [以下内容仅为本人在学习中的所感所想,本人水平有限目前尚处学习阶段,如有错误及不妥之处还请各位大佬指正,请谅解,谢谢!] 引言 动态规划虽然已经是对暴力算法的优化,但在某些比较特别的情况下,可以通过一 ...

随机推荐

  1. mysql:Windows修改MySQL数据库密码(修改或忘记密码)

    今天练习远程访问数据库时,为了方便访问,就想着把数据库密码改为统一的,以后我们也会经常遇到MySQL需要修改密码的情况,比如密码太简单.忘记密码等等.在这里我就借鉴其他人的方法总结几种修改MySQL密 ...

  2. C#/.NET这些实用的编程技巧你都会了吗?

    DotNet Exercises介绍 DotNetGuide专栏C#/.NET/.NET Core编程常用语法.算法.技巧.中间件.类库练习集,配套详细的文章教程讲解,助你快速掌握C#/.NET/.N ...

  3. QT 的 ModelView

    QApplication a(argc, argv); QDirModel model;    //QDirModel,   问文件目录树 QTreeView tree;    QListView l ...

  4. Vue入门记录(一)

    效果 本文为实现如下前端效果的学习实践记录: 实践 入门的最佳实践我觉得是先去看官网,官网一般都会有快速入门指引. 根据官网的快速上手文档,构建一个新的Vue3+TypeScript,查看新建的项目结 ...

  5. layui下拉框的数据如何直接从数据库提取(动态赋值)

    代码说明部分 第一步:先把layui官方给的模板粘到自己的前端注:下面的代码是我直接从layui官网粘过来的 <div class="layui-form-item"> ...

  6. 【ElasticSearch】数据迁移方案

    一.需求背景 ES环境要从单机迁移到集群上面 现在已有的数据也要搬过去,有几个索引三四千万数据大概 二.实现方案 有两种,使用ElasticDump和LogStash的ES插件 1.ElasticDu ...

  7. 【Java-GUI】05 绘图 Part1

    案例: package cn.dzz; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.Acti ...

  8. ECMO(体外膜氧合)的使用费用为什么会这么高?

    给一个大致的费用: 相关: https://www.bilibili.com/video/BV1rc411H7uT/ https://haokan.baidu.com/v?pd=wisenatural ...

  9. OpenAI内讧更多细节曝光:奥特曼离间董事会失败

    参考: https://www.thepaper.cn/newsDetail_forward_25512687 ============================== 根据 https://ww ...

  10. 在分布式nvidia cuda-pytorch中同时使用MPI和NCCL会造成死锁——分布式pytorch的backend不能同时使用MPI和NCCL

    参考原文: https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/mpi.html#inter-gpu-communication-wit ...