嗯,作为一只蒟蒻,今天再次学习了状压dp(学习借鉴的博客

但是,依旧懵逼··································

这篇学习笔记是我个人对于状压dp的理解,如果有什么不对的地方,希望大家指出。

闲话不多说,进入正题。

首先,在介绍状压dp之前,我们先来了解一下状态压缩(常用的为二进制,why?【因为其他的我不会】)。

什么是状态压缩呢?顾名思义,就是将数转换为二进制来进行一些操作。

基本操作:

看完基本操作,我们来看一下一些稍微复杂的操作。

操作 运算
取出整数n在二进制表示下的第k位 (n>>k)&1
取出整数n在二进制表示下的第0~k-1位(后k位)       n&((1<<k)-1)
取出整数n在二进制表示下的第k位取反  n^(1<<k)
取出整数n在二进制表示下的第k位赋值为1 n|(1<<k)
取出整数n在二进制表示下的第k位赋值为0 n&(~(1<<k))

大概就是这些了,如果有看不懂的地方大家可以手推一下。


下面我们来看一道状态压缩的题

P2622

题目描述

现有n盏灯,以及m个按钮。每个按钮可以同时控制这n盏灯——按下了第i个按钮,对于所有的灯都有一个效果。按下i按钮对于第j盏灯,是下面3中效果之一:如果a[i][j]为1,那么当这盏灯开了的时候,把它关上,否则不管;如果为-1的话,如果这盏灯是关的,那么把它打开,否则也不管;如果是0,无论这灯是否开,都不管。

现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。

输入输出格式

输入格式:

前两行两个数,n m

接下来m行,每行n个数,a[i][j]表示第i个开关对第j个灯的效果。

输出格式:

一个整数,表示最少按按钮次数。如果没有任何办法使其全部关闭,输出-1

输入输出样例

输入样例#1:

3
2
1 0 1
-1 1 0
输出样例#1:

2

说明

对于20%数据,输出无解可以得分。

对于20%数据,n<=5

对于20%数据,m<=20

上面的数据点可能会重叠。

对于100%数据 n<=10,m<=100


题目分析:

我们刚拿到这道题时,可能会没有思路(就像我一样)

这时候,如果我们在考试的时候实在没有思路的话就……输出-1吧!

好了,不闹了,回归正题。看到这个题的时候我们会想到搜索,这并非不是一个可能的解法。

再看一下题目结构,只有开和关的两种状态,所以我们就可以用0和1来表示,此时我们就可以用二进制来表示。

开——1,关——0

接下来,我们就要进行搜索了。

由于在初始时我们得到的状态全部都是开的,所以我们就需要n个1,这时我们就用 int num=(<<(n))-; 来表示n个1。

然后我们在搜索时去寻找开关为1/-1的状态,并在找到时进行判断和步数的统计,最后判断是否将所有状态全部改正即可。

代码:

 #include<bits/stdc++.h>
using namespace std;
template<typename type>
void scan(type &x){
type f=;x=;char s=getchar();
while(s<''||s>''){if(s=='-')f=-;s=getchar();}
while(s>=''&&s<=''){x=x*+s-'';s=getchar();}
x*=f;
}
const int maxn=;
struct node{
int step,zt;
};
int vis[maxn],mp[maxn][maxn];
int n,m; void bfs(int num){
queue<node>q;
node f;f.step=;f.zt=num;//初始状态
q.push(f);//入队
while(!q.empty()){
node u=q.front();
q.pop();
int pre=u.zt;
for(int i=;i<=m;i++){//枚举
int now=pre;
for(int j=;j<=n;j++){
if(mp[i][j]==){
if((<<(j-))&now){//如果第j位的开关为1,且当前的灯还开着,
now=now^(<<(j-));// 这时候将其状态修改
}}
else{ if(mp[i][j]==-){//如果第j位的开关为-1,且当前的灯关着,
now=((<<(j-))|now);// 这时候将其状态修改
}
}
}
f.zt=now;f.step=u.step+;
if(vis[now]==)continue;//打标记,防止重复
if(f.zt==){
vis[]=;//标记
printf("%d\n",f.step);
return ;
}
q.push(f);//入队
vis[now]=; //标记
} }
} int main(){
scan(n);scan(m);
int num=(<<(n))-;//找到n位1
for(int i=;i<=m;i++){
for(int j=;j<=n;j++){
scan(mp[i][j]);
}
}
bfs(num);
if(vis[]==){
printf("-1\n");
}
return ;
}

这样就可以了。

下面,我们来介绍一道十分基础的状压dp的题目

P1879


题目描述

Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1 ≤ N ≤ 12) square parcels. He wants to grow some yummy corn for the cows on a number of squares. Regrettably, some of the squares are infertile and can't be planted. Canny FJ knows that the cows dislike eating close to each other, so when choosing which squares to plant, he avoids choosing squares that are adjacent; no two chosen squares share an edge. He has not yet made the final choice as to which squares to plant.

Being a very open-minded man, Farmer John wants to consider all possible options for how to choose the squares for planting. He is so open-minded that he considers choosing no squares as a valid option! Please help Farmer John determine the number of ways he can choose the squares to plant.

农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。

遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。

John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)

输入输出格式

输入格式:

第一行:两个整数M和N,用空格隔开。

第2到第M+1行:每行包含N个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。

输出格式:

一个整数,即牧场分配总方案数除以100,000,000的余数。

输入输出样例

输入样例#1:

2 3
1 1 1
0 1 0
输出样例#1:

9

首先,我们在经历了第一题的练习,我们可以发现一些规律,如果题目给的数据范围并不是很大而且状态数量很多且都可以用0/1来表示,这时我们就可以去考虑去使用状态压缩。
所以我们就来考虑状压dp的方法,用以为数组来表示状态
代码:
 #include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=1e6+,N=,mod=1e8;
int n,m,a[N][N],b[N],dp[N][M];
void in(){
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++){
for(int j=;j<=m;j++){
scanf("%d",&a[i][j]);
}
}
}//读入
void update(){
for(int i=;i<=n;i++){
for(int j=;j<=m;j++){
b[i]=(b[i]<<)^a[i][j]^;//求取反码,判断是否为贫瘠土地,如果为1,则该土地贫瘠
}
}
}//求取贫瘠点
void judge(){
for(int i=;i<=(<<m)-;i++){
if(!(i&b[])&&!(i&(i<<))){
dp[][i]=;//第一行初始更新,满足条件
}
}
for(int i=;i<=n;i++){
for(int j=;j<=(<<m)-;j++){
for(int h=;h<=(<<m)-;h++){//枚举多次状态
if(h&j){
continue;//如果和上一行有交集,舍弃
}
if(h&(h>>)){
continue;//如果自己有相邻的,舍弃
}
if(h&(h<<)){
continue;//如果自己有相邻的,舍弃
}
if(h&b[i]){
continue;//如果有贫瘠的,舍弃
}
dp[i][h]+=dp[i-][j];
dp[i][h]=dp[i][h]%mod;//更新答案
}
}
}
ll ans=;
for(int j=;j<=(<<m)-;j++){
ans+=dp[n][j];
ans=ans%mod;
}
printf("ll%d",&ans);
}
int main(){
in();
update();
judge();
}

P1896

题目描述

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

注:数据有加强(2018/4/25)

输入输出格式

输入格式:

只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

输出格式:

所得的方案数

输入输出样例

输入样例#1:

3 2
输出样例#1:

16


读完题后,由于数据范围很小,但是状态数有很多,所以,我们就考虑来用状压dp。

代码:
 #include<bits/stdc++.h>
using namespace std;
template <typename type>
void scan(type &x){
type f=;x=;char s=getchar();
while(s<''||s>''){if(s=='-')f=-;s=getchar();}
while(s>=''&&s<=''){x=x*+s-'';s=getchar();}
x*=f;
}
#define ll long long
int n,m;
ll dp[][][],ki[],st[],ans,sum; void init(){
int cnt=(<<n)-;//在最多的情况下,时每一位都有国王
for(int i=;i<=cnt;i++){
if(!((i<<)&i)){//如果两国王不相邻,则符合状态
st[++ans]=i;
int t=i;
while(t){
ki[ans]+=t%;//统计
t/=;
}
}
}
} int main(){
scan(n);scan(m);
init();
for(int i=;i<=ans;i++){//先处理第一行
if(ki[i]<=m)//只有第一行的国王总数不超过国王总数才可行
dp[][i][ki[i]]=;
}
for(int i=;i<=n;i++){//从第二行进行状态转移
for(int j=;j<=ans;j++){//这里和下一遍的枚举为的是处理 当前行和上一行的状态,来判断是否符合条件
for(int k=;k<=ans;k++){
if(st[j]&st[k])continue;//上下
if(st[j]&(st[k]<<))continue;//右上
if((st[j]<<)&st[k])continue;//左上
for(int l=;l<=m;l++){
if(ki[j]+l>m)continue;//如果超出了范围,舍去
dp[i][j][ki[j]+l]+=dp[i-][k][l];//否则进行转移
}
}
}
}
for(int i=;i<=n;i++){
for(int j=;j<=ans;j++){
sum+=dp[i][j][m];//统计答案
}
}
printf("%lld\n",sum);
return ;
}

2019.4.27更新

P2051

题目描述

这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!

输入输出格式

输入格式:

一行包含两个整数N,M,之间由一个空格隔开。

输出格式:

总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。

输入输出样例

输入样例#1:

1 3
输出样例#1:

7

说明

样例说明

除了3个格子里都塞满了炮以外,其它方案都是可行的,所以一共有2*2*2-1=7种方案。

数据范围

100%的数据中N和M均不超过100

50%的数据中N和M至少有一个数不超过8

30%的数据中N和M均不超过6


分析:

这道题刚开始看时,我并没有多好的思路(托腮),只是去胡乱的打一打

这道题其实是是一道状压dp(说实话,我真没想到→_→)

这怎么是一道状压呢,下面来给大家解释一下。

首先,我们都知道每一列最多只有两个炮(为啥?仨炮都能轰着玩了好吧)

所以,这个时候也就有三个状态一个是这一列有0/1/2,所以我们就要想一想我们怎么设置状态

我们用 dp[i][j][k] 来表示状态,其中i表示行数,j表示有一个棋子的列数,k表示有两个棋子的列数。

这时候我们就来考虑状态转移方程

首先,不放棋子的话就是 dp[i][j][k]+=dp[i-][j][k];

其次放一个棋子的情况

if(k>=)dp[i][j][k]+=(dp[i-][j+][k-]*(j+));//选择放一个棋子,且放在有一个棋子的列上
if(j>=)dp[i][j][k]+=(dp[i-][j-][k]*(m-j-k+));//选择放一个棋子,且放在没有棋子的列上

放两个棋子:

if(k>=)dp[i][j][k]+=(dp[i-][j][k-]*j*(m-j-k+));//选择放两个棋子,一个放在有棋子的列上,一个放在没有棋子的列上。
if(j>=)dp[i][j][k]+=(dp[i-][j-][k]*C(m-j-k+));//选择放两个棋子,全部放在没有棋子的列上。
if(k>=)dp[i][j][k]+=(dp[i-][j+][k-]*C(j+));//选择放两个棋子,全部放在有一个棋子的列上。

代码:

 #include<bits/stdc++.h>
#define ll long long
using namespace std;
template <typename type>
void scan(type &x){
type f=;x=;char s=getchar();
while(s<''||s>''){if(s=='-')f=-;s=getchar();}
while(s>=''&&s<=''){x=x*+s-'';s=getchar();}
x*=f;
}
ll dp[][][];
ll n,m,mod=,ans;
ll C(ll x){
return (x*(x-)/)%mod;
}
int main(){
scan(n),scan(m);
dp[][][]=;
for(int i=;i<=n;i++){//表示每一行
for(int j=;j<=m;j++){
for(int k=;k<=m-j;k++){
dp[i][j][k]+=dp[i-][j][k];
if(k>=)dp[i][j][k]+=(dp[i-][j+][k-]*(j+));//选择放一个棋子,且放在有一个棋子的列上
if(j>=)dp[i][j][k]+=(dp[i-][j-][k]*(m-j-k+));//选择放一个棋子,且放在没有棋子的列上
if(k>=)dp[i][j][k]+=(dp[i-][j][k-]*j*(m-j-k+));//选择放两个棋子,一个放在有棋子的列上,一个放在没有棋子的列上。
if(j>=)dp[i][j][k]+=(dp[i-][j-][k]*C(m-j-k+));//选择放两个棋子,全部放在没有棋子的列上。
if(k>=)dp[i][j][k]+=(dp[i-][j+][k-]*C(j+));//选择放两个棋子,全部放在有一个棋子的列上。
dp[i][j][k]%=mod;
}
}
}
for(int i=;i<=m;i++){
for(int j=;j<=m;j++){
ans+=dp[n][i][j];
ans%=mod;
}
}
printf("%lld\n",ans); return ;
}

持续更新(当然,如果我还记得的话,欢迎评论~_~)……

状压dp(状态压缩&&dp结合)学习笔记(持续更新)的更多相关文章

  1. 【bzoj1076】[SCOI2008]奖励关 期望dp+状态压缩dp

    题目描述 你正在玩你最喜欢的电子游戏,并且刚刚进入一个奖励关.在这个奖励关里,系统将依次随机抛出k次宝物,每次你都可以选择吃或者不吃(必须在抛出下一个宝物之前做出选择,且现在决定不吃的宝物以后也不能再 ...

  2. BZOJ 2595: [Wc2008]游览计划 [DP 状压 斯坦纳树 spfa]【学习笔记】

    传送门 题意:略 论文 <SPFA算法的优化及应用> http://www.cnblogs.com/lazycal/p/bzoj-2595.html 本题的核心就是求斯坦纳树: Stein ...

  3. 数据分析之Pandas和Numpy学习笔记(持续更新)<1>

    pandas and numpy notebook        最近工作交接,整理电脑资料时看到了之前的基于Jupyter学习数据分析相关模块学习笔记.想着拿出来分享一下,可是Jupyter导出来h ...

  4. [读书]10g/11g编程艺术深入体现结构学习笔记(持续更新...)

    持续更新...) 第8章 1.在过程性循环中提交更新容易产生ora-01555:snapshot too old错误.P257 (这种情况我觉得应该是在高并发的情况下才会产生) 假设的一个场景是系统一 ...

  5. Semantic ui 学习笔记 持续更新

    这个semantic 更新版本好快~ 首先是代码的标识<code></code> 具体样式就是红框这样的 圈起来代码感觉不错 不过要在semantic.css里在加上如下样式~ ...

  6. R语言的学习笔记 (持续更新.....)

    1. DATE 处理 1.1 日期格式一个是as.Date(XXX) 和strptime(XXX),前者为Date格式,后者为POSIXlt格式 1.2 用法:as.Date(XXX,"%Y ...

  7. angularjs1学习笔记--持续更新

    angularJS使用的MVC为何不属于二十三种设计模式之一? MVC被GoF (Gang of Four,四人组, <Design Patterns: Elements of Reusable ...

  8. Git学习笔记(持续更新)

    1.强制同步为远程的代码 远程仓库回退了commit的情况下(第2条描述之情况),强制同步远程的代码到本地 #更新远程最新的所有代码,但是不merge或者rebase git fetch --all ...

  9. ggplot2 学习笔记 (持续更新.....)

    1. 目前有四种主题 theme_gray(), theme_bw() , theme_minimal(),theme_classic() 2. X轴设置刻度 scale_x_continuous(l ...

  10. # MongoDB学习笔记(持续更新)

    启动mongo服务 sodo mongo 显示数据库(显示数据库名称和大小,单位GB) > show dbs admin (empty) local 0.078GB test 0.078GB t ...

随机推荐

  1. MongoDB 基础(2019年开篇)

    MongoDB基础知识: 1.什么是MongoDB NoSQL(NoSQL=Not Only SQL),意即"不仅仅是SQL". MongoDB是一个介于关系数据库和非关系数据库之 ...

  2. 走进Java Map家族 (1) - HashMap实现原理分析

    在Java世界里,有一个古老而神秘的家族——Map.从底层架构到上层应用,他们活跃于世界的每一个角落.但是,每次出现时,他们都戴着一张冷硬的面具(接口),深深隐藏着自己的内心.所有人都认识他们,却并非 ...

  3. 搭建 structs2 环境

    前言 环境: window 10 ,JDK 1.8 ,Tomcat 7 ,MyEclipse 2014 pro 搭建 SSH 环境的步骤 创建 JavaWeb 项目 导入 structs2 的jar包 ...

  4. Jenkins 使用Tfs 插件出现 MappingConflictException 错误问题解决

    FATAL: com.microsoft.tfs.core.clients.versioncontrol.exceptions.MappingConflictException: The path C ...

  5. 免费了 -- EXCEL插件 智表ZCELL 普及版V1.0 发布了!!!

    智表(zcell)是一款浏览器仿excel表格jquery插件.智表可以为你提供excel般的智能体验,支持双击编辑.设置公式.设置显示小数精度.下拉框.自定义单元格.复制粘贴.不连续选定.合并单元格 ...

  6. CDH集成Kafka,两种方式:离线、在线

    1.离线 先下载相应版本的kafka http://archive.cloudera.com/kafka/parcels/ 然后放置相应目录,如下图: 然后直接添加组件即可 2.在线 配置相应的kaf ...

  7. Hybrid APP之Native和H5页面交互原理

    Hybrid APP之Native和H5页面交互原理 Hybrid APP的关键是原生页面与H5页面直接的交互,如下图,痛过JSBridge,H5页面可以调用Native的api,Native也可调用 ...

  8. js获取数组中最大值,最小值

    遍历方法 var arr =[12,14,34,566,34,98,77] var max = arr[0]; for(var i=0;i<arr.length;i++){ if(max< ...

  9. 毕业设计(4):基于MicroPython的超声波倒车雷达系统

    前言 倒车雷达是汽车驻车或者倒车时的安全辅助装置,能以声音或者更为直观的显示告知驾驶员周围障碍物的情况,解除了驾驶员驻车.倒车和起动车辆时前后左右探视所引起的困扰,并帮助驾驶员扫除了视野死角和视线模糊 ...

  10. CentOS7.x安装kickstart

    kickstart简介 kickstartkickstart是RedHat公司开源的软件,所以对CentOS兼容性最好,是一种无人值守的安装方式就是将手动安装配置的步骤,记录到文件中,然后通过kick ...