嗯,作为一只蒟蒻,今天再次学习了状压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. oppo7.0系统手机(亲测有效)激活Xposed框架的流程

    对于喜欢钻研手机的朋友而言,很多时候会使用到xposed框架及种类繁多功能极强的模块,对于5.0以下的系统版本,只要手机能获得root权限,安装和激活xposed框架是异常简单的,但随着系统版本的升级 ...

  2. 关于ORACLE数据库名以及数据实例名等几个重要概念

    在Oracle中有关数据库和数据库实例的几个重要概念,有时候如果理解不是很深或者对其疏忽.混淆了,还真容易搞错或弄不清其概念,下面就数据库实例名.数据库名.数据库域名.数据库服务名.全局数据库名几个概 ...

  3. mssql2008 r2 修改默认端口

    1.点击“开始”-“所有程序”-“Microsoft SQL Server 2008R2”-“配置工具”-“SQL Server配置管理器” 2.在打开的“SQL Server配置管理器”窗口中,在左 ...

  4. sprintboot 中占位符及多环境配置

    (原) 关于springboot中多环境配置问题 1.在application.properties文件中通过 spring.profiles.active=... 选择系统所要加载的配置文件,这里的 ...

  5. Codeforces #541 (Div2) - F. Asya And Kittens(并查集+链表)

    Problem   Codeforces #541 (Div2) - F. Asya And Kittens Time Limit: 2000 mSec Problem Description Inp ...

  6. openstack 2019/4/28

    官网参考地址:https://docs.openstack.org/keystone/queens/install/index-rdo.html (但愿能看懂) 环境:这个部分解释如何按示例架构配置控 ...

  7. 初识Haskell 二:基本操作符、类型Type、数据结构

    对Discrete Mathematics Using a Computer的第一章Introduction to Haskell进行总结.环境Windows 1. 在安装了ghci后,便可以进行Ha ...

  8. java中异常处理finally和return的执行顺序

    根据个人亲自测试,得出如下结果: 1.try,catch中有没有return,finnally都会执行,而且是先执行try,catch后,最后执行finnally语句: 2.如果finnally中有r ...

  9. php框架之thinkphp

    日常开发中经常使用thinkphp5进行开发工作,总结一些使用中遇到的问题和使用的东西 1. web内置服务 V5.1.5+版本开始,增加了启动内置服务器的指令,方便测试 >php think ...

  10. JS学习笔记:(三)JS执行机制

    首先我们先明确一点:JavaScript是一门单线程语言.单线程也就是说同一时间只能执行一个任务,所有的任务都必须排队顺序执行.那么如果一个任务耗时很长,阻塞了其它任务的执行,就会给用户造成不友好的体 ...