状态压缩DP入门

什么是状压DP:
动态规划的状态有时候比较恶心,不容易表示出来,需要用一些编码技术,把状态压缩的用简单的方式表示出来。
典型方式:当需要表示一个集合有哪些元素时,往往利用2进制用一个整数表示。
动态规划本来就很抽象,状态的设定和状态的转移都不好把握,而状态压缩的动态规划解决的就是那种状态很多,不容易用一般的方法表示的动态规划问题,这个就更加的难于把握了。难点在于以下几个方面:状态怎么压缩?压缩后怎么表示?怎么转移?是否具有最优子结构?是否满足后效性?涉及到一些位运算的操作,虽然比较抽象,但本质还是动态规划。找准动态规划几个方面的问题,深刻理解动态规划的原理,开动脑筋思考问题。这才是掌握动态规划的关键。
分析

运算名符号效果
&按位与如果两个相应的二进制位都为1,则该位的结果值为1,否则为0
l按位或两个相应的二进制位中只要有一个为1,该位的结果值为1
^按位异或若参加运算的两个二进制位值相同则为0,否则为1
~取反~是一元运算符,用来对一个二进制数按位取\反,即将0变1,将1变0
<<左移用来将一个数的各二进制位全部左移N位,右补0
*>>右移将一个数的各二进制位右移N位,移到右端 的低位被舍弃,对于无符号数,高位补0

下面来看三道题目:
例题一:
[POJ3254]Corn Fields(其实就是牛吃草)
题目大意
一个矩阵里有很多格子,每个格子有两种状态,可以放牧和不可以放牧,可以放牧用1表示,否则用0表示,在这块牧场放牛,要求两个相邻的方格不能同时放牛(不包括斜着的),即牛与牛不能相邻。问有多少种放牛方案(一头牛都不放也是一种方案)
输入
1<=n<=12,1<=m<=12
输出
一个mod100000000的整数
样例输入
2 3
1 1 1
0 1 0
样例输出
9
分析
从题意我们可以知道牛与牛之间不能相邻,我们可以很容易的想到可以一行一行的递推,因为每只牛能不能放只与上一行和当前这一行有关。
所以dp中有一个维度是用来表示第几行的,还有一个维度就是用来表示哪一行的状态的。
假设有m列,则状态最多只有(1《m-1)种,这是显然的。因为他每一列只有放和不放这两种决策。
那么怎么表示状态是个关键问题,这就要用到状态压缩了,用二进制来表示某个状态,比如
0101代表的就是1、3列不放牛,2、4列放牛。这样不仅省空间省代码还省时间。
要用位运算是必须的。
参考代码

#include <cstdio>  
#include <cstring>  
const int N = 13;  
const int M = 1<<N;  
const int mod = 100000000;  
int st[M],map[M];  ///分别存每一行的状态和给出地的状态  
int dp[N][M];  //表示在第i行状态为j时候可以放牛的种数  
bool judge1(int x)  //判断二进制有没有相邻的1  
{  return (x&(x<<1));  
}  
bool judge2(int i,int x)  
{  return (map[i]&st[x]);  
}  
int main()  
{  int n,m,x;  while(~scanf("%d%d",&n,&m))  {  memset(st,0,sizeof(st));  memset(map,0,sizeof(map));  memset(dp,0,sizeof(dp));  for(int i=1;i<=n;i++)  {  for(int j=1;j<=m;j++){  scanf("%d",&x);  if(x==0)  map[i]+=(1<<(j-1));  }  }  int k=0;  for(int i=0;i<(1<<m);i++){  if(!judge1(i))  st[k++]=i;  }  for(int i=0;i<k;i++)  {  if(!judge2(1,i))  dp[1][i]=1;  }  for(int i=2;i<=n;i++)  {  for(int j=0;j<k;j++)  {  if(judge2(i,j))  //判断第i行 假如按状态j放牛的话行不行。  continue;  for(int f=0;f<k;f++)  {  if(judge2(i-1,f))   //剪枝 判断上一行与其状态是否满足  continue;  if(!(st[j]&st[f]))  dp[i][j]+=dp[i-1][f];  }  }  }  int ans=0;  for(int i=0;i<k;i++){  ans+=dp[n][i];  ans%=mod;  }  printf("%d\n",ans);  }  return 0;  
}  

例题二:
[POJ3311]Hie With The Pie
题目大意:
一个送外卖的人,从0点出发,要经过所有的地点然后再回到店里(就是0点),求最少花费的代价。
输入
1<=n<=10
输出
一个整数,代表最小花费。
样例输入
3
0 1 10 10
1 0 1 2
10 1 0 10
10 2 10 0
0
样例输出
8
分析
怎么做?我们可以先从暴力来分析分析。

搜索解法:这种解法其实就是计算排列子集树的过程。从0点出发,要求遍历123点后回到0点。以不同的顺序来依次遍历123点就会导出不同的路径(0->1->2->3->00->1->3->2->0等等),总共有3!=6条路径需要考虑,从中选出最短的那条就是所求。搜索解法的时间复杂度为 O(n!) 。

需要注意的是题目显然给的是个邻接矩阵,并不代表各点之间的距离,所以我们需要先Floyd求出各点的最短路

动归解法:仔细观察搜索解法的过程,其实是有很多重复计算的。比如从0点出发,经过12345点后回到0点。那么0->1->2->(345三个点的排列)->00->2->1->(345三个点的排列)->0就存在重复计算(345三点的排列)->0路径集上的最短路径。只要我们能够将这些状态保存下来就能够降低一部分复杂度。下面就让我们用动归来求解这一问题。记dp(S,v)为走完了集合S后最后停留在v点的最小花费。

我们不难得出递推方程式为

dp[S][v] = min(dp[S除去点v)][k] + dis[k][v],dp[S][v])

好吧o(╯□╰)o,和floyd确实有那么二两相似。
参考代码

#include<iostream>  
#define INF 100000000  
using namespace std;  
int dis[12][12];  
int dp[1<<11][12];  
int n,ans,_min;  
int main()  
{  //freopen("in.txt","r",stdin);  while(scanf("%d",&n) && n)  {  for(int i = 0;i <= n;++i)  for(int j = 0;j <= n;++j)  scanf("%d",&dis[i][j]);  for(int k = 0;k <= n;++k)  for(int i = 0;i <= n;++i)  for(int j = 0;j <= n;++j)  if(dis[i][k] + dis[k][j] < dis[i][j])  dis[i][j] = dis[i][k] + dis[k][j];  for(int S = 0;S <= (1<<n)-1;++S)//枚举所有状态,用位运算表示  for(int i = 1;i <= n;++i)  {  if(S & (1<<(i-1)))//状态S中已经过城市i  {  if(S == (1<<(i-1)))   dp[S][i] = dis[0][i];//状态S只经过城市I,最优解自然是从0出发到i的dis,这也是DP的边界  else//如果S有经过多个城市  {  dp[S][i] = INF;  for(int j = 1;j <= n;++j)  {  if(S & (1<<(j-1)) && j != i)//枚举不是城市I的其他城市  dp[S][i] = min(dp[S^(1<<(i-1))][j] + dis[j][i],dp[S][i]);  //在没经过城市I的状态中,寻找合适的中间点J使得距离更短,和FLOYD一样  }  }  }  }  ans = dp[(1<<n)-1][1] + dis[1][0];  for(int i = 2;i <= n;++i)  if(dp[(1<<n)-1][i] + dis[i][0] < ans)  ans = dp[(1<<n)-1][i] + dis[i][0];  printf("%d/n",ans);  }  return 0;  
}  

例题三
[POJ1185]炮兵阵地
描述
司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用”H” 表示),也可能是平原(用”P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
这里写图片描述
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入
第一行输出数据测试组数X(0~X~100)
接下来每组测试数据的第一行包含两个由空格分割开的正整数,分别表示N和M; 接下来的N行,每一行含有连续的M个字符(‘P’或者’H’),中间没有空格。按顺序表示地图中每一行的数据。0<=N <= 100;0<=M <= 10。
输出
每组测试数据输出仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。
样例输入
1
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
样例输出
6
参考代码

#include <cstdio>  
#include <iostream>  
#include <cstring>  
#include <algorithm>  
using namespace std;  
const int N = 105;  
int Map[N];  
int dp[N][65][65];  //dp[i][j][k]表示放第i行时,第i行为第j个状态,第i-1行为第k个状态最多可以放多少个炮兵  
int s[N], num[N];  
int n, m, p;  bool check(int x) {  //判断本行的炮兵是否互相攻击  if(x & (x >> 1)) return false;  if(x & (x >> 2)) return false;  return true;  
}  int Count(int x)
{int i=1, ans=0; while(i<=x){if(x&i) ans++;  i<<=1;  }  return ans;  
}  
void Init()
{  p=0;  memset(s,0,sizeof(s));  memset(num,0,sizeof(num));  for(int i=0;i<(1<<m);i++){if(check(i)){  s[p]=i;num[p++]=Count(i);  //计算状态为x时可以放多少个炮兵  }  }
}  int main() {  char ch;scanf("%d%d", &n, &m);memset(dp, 0, sizeof(dp));  memset(Map, 0, sizeof(Map));  for(int i=0;i<n;i++){  for(int j=0;j<m;j++){  cin>>ch;if(ch == 'H')  Map[i]+=(1<<(m-1-j));//P为0,H为1  } }  Init();//预处理出合法状态  for(int i = 0; i < p; i++) //求第一行最多放多少  if(!(Map[0]&s[i]))// 不在山上 dp[0][i][0]=num[i];  for(int i = 0; i < p; i++)//前两行最多放多少  {if(!(Map[1]&s[i]))//不与第一行冲突 {  for(int j=0;j<p;j++){  if((!(s[i]&s[j])))//一二行不冲突 {  dp[1][i][j]=max(dp[1][i][j],dp[0][j][0]+num[i]);}  }  }  }  for(int r=2;r<n;r++)//枚举行数  {for(int i=0;i<p;i++)//当前行的状态 {   if(!(s[i]&Map[r]))//不在山上 {  for(int j = 0; j < p; j++) //上一行的状态  {  if(!(s[j] & Map[r-1]))//不在山上 {  if(!(s[i] & s[j]))//不与当前行冲突 {  for(int k = 0; k < p; k++)//上上一行的状态{    if(!(s[k] & Map[r-2])) //不在山上 {  if(!(s[j] & s[k])) //不与上一冲突 {  if(!(s[i] & s[k]))//不与当前行冲突 {  dp[r][i][j]=max(dp[r][i][j],dp[r-1][j][k]+num[i]);  }  }  }  }  }  }  }  }  }  }  int ans = 0;  for(int i = 0; i < p; i++){  for(int j = 0; j < p; j++){  if(ans<dp[n-1][i][j])  ans=dp[n-1][i][j];  }  }  printf("%d\n", ans);   return 0;  
}  

转载于:https://www.cnblogs.com/ibilllee/p/7651971.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/268282.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

InitializingBean、@PostConstruct、@Bean(initMethod = “init“)和构造方法 执行优先级比较

InitializingBean 1、InitializingBean接口为bean提供了初始化方法的方式&#xff0c;它只包括afterPropertiesSet方法&#xff0c;凡是继承该接口的类&#xff0c;在初始化bean的时候都会执行该方法。 2、spring初始化bean的时候&#xff0c;如果bean实现了InitializingBean接…

Windows 7 镜像制作过程

首先准备两台电脑&#xff0c;一台作为样机&#xff0c;一台作为技术人员电脑&#xff0c;技术人员电脑安装了Windows AIK第一部分、系统安装配置1、安装Windows 7 操作系统&#xff0c;步骤略过2、启用Administrator&#xff0c;使用Administrator登陆&#xff0c;然后在控制面…

论文笔记——Deep Model Compression Distilling Knowledge from Noisy Teachers

论文地址&#xff1a;https://arxiv.org/abs/1610.09650 主要思想 这篇文章就是用teacher-student模型&#xff0c;用一个teacher模型来训练一个student模型&#xff0c;同时对teacher模型的输出结果加以噪声&#xff0c;然后来模拟多个teacher&#xff0c;这也是一种正则化的方…

mysql清空全表数据建议直接用truncate,效率上truncate远高于delete

如果是清空全表数据建议直接用truncate&#xff0c;效率上truncate远高于delete&#xff0c;应为truncate不走事务&#xff0c;不会锁表&#xff0c;也不会生产大量日志写入日志文件&#xff1b;truncate table table_name 后立刻释放磁盘空间&#xff0c;并重置auto_increment…

[你必须知道的css系列]第一回:丰富的利器终结篇:选择符的组合关系及选择符总结...

介绍了这么多选择符&#xff0c;其实选择符的使用最大的优势不是单枪匹马奋斗&#xff0c;而应该是针对不同的页面结构组合成各种方阵。其主要方式体现在针对性使用类选择符或者 ID选择符、选择符群组及选择符组合这3种方式。一、针对性使用类选择符或者 ID选择符主要作用于类选…

SQL中delete和update后加 Limit是个好习惯

在业务场景要求高的数据库中&#xff0c;对于单条删除和更新操作&#xff0c;在 delete 和 update 后面加 limit 1 绝对是个好习惯。比如&#xff0c;在删除执行中&#xff0c;第一条就命中了删除行&#xff0c;如果 SQL 中有 limit 1&#xff1b;这时就 return 了&#xff0c;…

问题步骤记录器——“懒教师”的好帮手

场景&#xff1a;电话响&#xff0c;接通电话&#xff0c;电话另一端&#xff1a;我的电脑又怎么怎么了&#xff0c;为什么我的***弄不出那样的效果&#xff1f;请问***要怎样操作&#xff1f;感悟&#xff1a;虽然不是大虾&#xff0c;但由于众多同学当中&#xff0c;我仍然靠…

CCNA配置试验之七 PPP中PAP和CHAP的验证

PPP支持NCPC&#xff08;网络控制协议&#xff09;和LCP&#xff08;链路控制协议&#xff09;PPP的验证方式分为PAP二次握手明文传输和CHAP三次握手密文传输。试验配置PAP和CHAP的验证&#xff1a;试验配置如下&#xff1a;R1&#xff08;CHAP&#xff09;Router>enRouter#…

sql优化批量插入性能提升

建议批量插入 批量提交 INSERT into book VALUES(5,"A"),(6,"B");多条提交 INSERT into book VALUES(5,"A"); INSERT into book VALUES(6,"B") 理由 默认新增SQL有事务控制&#xff0c;导致每条都需要事务开启和事务提交&#xff0…

Unity3D学习(五):实现一个简单的视觉感知

前言 在很多第一人称或者第三人称射击游戏的单人模式中&#xff0c;玩家的乐趣往往来源于和各式各样的AI敌人的战斗。而战斗的爆发很多时候是因为这些AI在“看见”玩家后就会立即做出反应&#xff0c;比如开火、呼叫同伴、躲藏或者逃跑等。 所以这些AI到底是如何探测&#xff0…

BeanFactory中String FACTORY_BEAN_PREFIX = ““;解析

此接口定义了Bean的一些基本信息判断和获取bean比如 isSingleton isTypeMatch 还有一个需要注意的地方&#xff0c;看这个常量: String FACTORY_BEAN_PREFIX "&"; 如果我们在获取Bean的时候&#xff0c;使用&则获取的是FactoryBean本身对象&#xff0c;否则…

.NET Core简介

转载于:https://www.cnblogs.com/wxc-kingsley/p/7660878.html

spring中DefaultListableBeanFactory是bean加载的核心部分,是spring注册和加载bean的默认实现方式

DefaultListableBeanFactory介绍 BeanFactory是个Factory&#xff0c;也就是IOC容器或对象工厂&#xff0c;而DefaultListableBeanFactory是Bean工厂的一个默认实现&#xff0c;DefaultListableBeanFactory提供了原始的BeanFactory的功能&#xff0c;如&#xff1a;对外提供ge…

使用MyBatis集成阿里巴巴druid连接池(不使用spring)

在工作中发现mybatis默认的连接池POOLED&#xff0c;运行时间长了会报莫名其妙的连接失败错误。因此采用阿里巴巴的Druid数据源&#xff08;码云链接 &#xff0c;中文文档链接&#xff09;。 mybatis更多数据源参考博客链接 。 1 环境 eclipse mars2 maven3.3.9 mysql5.7 2 …

docker search从Docker Hub查找(搜索)镜像

镜像搜索 docker search 名称 列表说明&#xff1a;NAME&#xff1a;镜像名(镜像仓库源的名称)DESCRIPTION&#xff1a;对该镜像的描述STARS&#xff1a;类似 Github 里面的 star&#xff0c;表示点赞、喜欢的意思OFFICIAL&#xff1a;是否 docker 官方发布AUTOMATED&#xff1…

mysql求差集

mysql怎么求差集? mysql如何查询两个字段数不同的表中数据不一致的记录 一般可用NOT EXISTS&#xff08;非存在子句&#xff09;或 LEFT JOIN左&#xff08;右&#xff09;连接后所产生空字段值来筛选两表的差集 classinfo表 student表 1、NOT EXISTS not exists在比对字段…

html5表单实现简单计算器

html5表单实现简单计算器 <!DOCTYPE html><html><head> <title>this is a html page</title></head><body> <form οninput"res.value n1.valueAsNumber * n2.valueAsNumber"> <input type"number" …

python学习笔记(15)循环设计

python学习笔记&#xff08;15&#xff09;循环设计 原链&#xff1a;http://www.cnblogs.com/vamei/archive/2012/07/09/2582435.html 注意&#xff1a;zip()在python2 3里面不一致 #第15讲 循环设计#循环在前面是有学习的&#xff0c;简单的循环for i in range(10):print (i*…

docker search 镜像关键词

查找镜像 docker search 镜像关键词 比如我要搜索nginx docker search nginx 参数说明&#xff1a; NAME: 镜像仓库源的名称 DESCRIPTION: 镜像的描述 OFFICIAL: 是否 docker 官方发布 stars: 类似 Github 里面的 star&#xff0c;表示点赞、喜欢的意思。 AUTOMATED: …