首页 > 其他分享 >【学习笔记】状压DP

【学习笔记】状压DP

时间:2024-09-12 19:24:21浏览次数:1  
标签:状态 题目 int 奶酪 状压 笔记 当前 DP

状态压缩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;
 }

标签:状态,题目,int,奶酪,状压,笔记,当前,DP
From: https://www.cnblogs.com/GSNforces/p/18410887

相关文章

  • MathType纯新手安装笔记自述
    一、找到自己E盘中下载的Mathtype7.4安装包及补丁二、安装MathType-win-zh三、管理员身份运行MathType7PJ四、打开WORD——》点击选项——》点击信任中心——》点击信任中心设置——》点击受信任位置五、双击右侧用户位置的第三行,弹出窗口,复制路径。六、按键“win+e”七......
  • 3D异常检测最新论文《Complementary Pseudo Multimodal Feature for Point Cloud Anom
        本文是曹云康24年投稿至《PattenRecognition》的文章,是目前在MVTec3D-AD数据集上的3D异常检测SOTA。之所以被分类到3D异常检测类别,是因为这篇文章中仅使用了点云数据进行检测,未使用RGB模态。同样,文章中也指出了它所使用的多模态其实是“伪模态”,是将点云投影到2......
  • MySQL学习笔记(四)MySQL慢查询优化
    慢日志查询慢速查询日志由执行时间超过long_query_time几秒并且至少需要min_examined_row_limit检查行的SQL语句组成long_query_timeSELECT@@long_query_time;--默认是10单位sSETGLOBALlong_query_time=1;--设置超过1s就算慢查min_examined_row_limitSEL......
  • 【读书笔记-《30天自制操作系统》-18】Day19
    本篇内容涉及到文件与文件系统,以及应用程序的运行。首先实现type命令,读取文件并显示;接下来导入对FAT文件系统的支持,实现读取大小512字节以上,存放在不连续扇区中的文件。在此基础上,最终实现读取并运行应用程序。1.type命令实现type命令是Windows命令行中用于读取并显示文......
  • Linux指令记不住的笔记
    ls查看当前路径下内容cd下一级路径名称或者别的路径进入下一级或别的路径cd..退回上一级路径rm文件名删除文件,文件名可以带路径rmdir文件夹名删除文件夹chmod更改文件或目录的权限rwx分别是读(read)、写(write)和执行(execute)ugoa分别是所有者(owner)、所在组(gr......
  • 知攻善防 Web2 应急靶机笔记
    知攻善防Web2应急靶机笔记概述这是一台知攻善防实验室的应急响应靶机,主要还是练习应急响应的一些技巧,熟悉一些应急所用到的工具。靶机可以关注他们的公众号《知攻善防实验室》进行获取题目欢迎使用知攻善防实验室解题系统:在解题前,请确保您已解的一下内容:1.攻击者的I......
  • Ethereum学习笔记 ---- 多重继承中的 C3线性化算法
    目录举个反例分析错误原因举个正例分析solidity中的多重继承多重继承合约的storagelayout学习solidity合约多重继承时,官方文档介绍solidity采用C3线性化算法来确定多重依赖中的继承顺序。维基百科上有很好的说明:C3线性化C3linearization下面通过实验来深入理解一下......
  • NOIP2024集训Day27 DP常见模型4 - 树形
    NOIP2024集训Day27DP常见模型4-树形E.[COCI2014-2015#1]Kamp首先只考虑一个点,发现如果回到原来位置是比较好搞的,就每次走完子树的里面要的就上来,如果子树里面没有要走的就不走。(大概是\(f_x=\sumf_y+2\cdote_x\),因为要走过去走回来,注意\(y\)要保证子树里面有人)......
  • elasticsearch学习笔记整理(含下面总结的面试题)
    elasticsearch是一个全文检索的搜索引擎Elasticsearch是一个基于Lucene的搜索服务器ES可以做全文检索、模糊查询(搜索)、数据分析(提供分析语法,例如聚合)。es是不能使用root用户进行启动的,要新创建一个用户才行创建用户:useraddqianfeng设置密码:passwdqianfeng早期es的结构......
  • MySQL学习笔记(三)InnoDB索引
    索引概念        索引在关系型数据库中,是一种单独的、物理的对数据库表中的一列或者多列值进行排序的一种存储结构,它是某个表中一列或者若干列值的集合,还有指向表中物理标识这些值的数据页的逻辑指针清单。        索引的作用相当于图书的目录,可以根据目......