状态压缩学习笔记

使用计算机中二进制的特性,将一个难以表达的状态,一般为有或没有( 101 | 0 ),变成二进制。例如 15(10)=1111(2)15_{(10)}=1111_{(2)}

枚举 0(2)1111111111(2)0_{(2)}\sim1111111111_{(2)} 就可以实现 010230\sim 1023 的状态。

状压 dp

例题:吃奶酪

定义 dpj,idp_{j,i} 为走过的最后一个点为 jj,状态为 ii 的最短路径。状态就是表示每个点是否已经走过。例如 1515 表示 1111(2)1111_(2),也就是 141\sim4 都走过了。

枚举每一种状态 ii,然后枚举 jj ,该状态的结尾路径在点 jj 上,最后枚举 kk,从点 kk 转移过来。

比如 j=1,k=5j=1,k=5 时,就是从 515\to1。之前的状态就是未走过 11 时的状态。一般来说,第 xx 个点就设为二进制中第 xx 低位,即 2x12^{x-1},所以为 dp5,i20dp_{5,i-2^{0}},所需要的代价就是 515\to1 的距离。

形式化地,本题状态转移方程就是:

dpj,i=min(dpj,i,dpk,i2j1+disj,k)\large{dp_{j,i}=\min(dp_{j,i},dp_{k,i-2^{j-1}}+dis_{j,k})}

实现时还要注意 j,kj,k 是否符合状态,当 j=kj=kk,ik,i 在状态 ii 中未走过时,都是不合法的。一般地,常使用位运算知识解决。


#include<bits/stdc++.h>
using namespace std;
const int N = 16;
int n;
double a[N][N];
double x[N],y[N];
double dp[N][1<<N],ans=1e100;
int main() {
	cin>>n;
	for(int i=1;i<=n;i++) {
		cin>>x[i]>>y[i];
	}
	memset(dp,0x7f,sizeof dp);
	for(int i=0;i<=n;i++) {
		for(int j=0;j<=n;j++) {
			a[i][j]=sqrt((x[j]-x[i])*(x[j]-x[i])+(y[j]-y[i])*(y[j]-y[i]));
		}
	}
	for(int i=1;i<=n;i++) {
		dp[i][1<<i-1]=a[0][i];//初始化 
	}
	for(int i=0;i<1<<n;i++) {//状态
		for(int j=0;j<=n;j++) {//到哪
			if((i&(1<<j-1))==0) continue;
			//该点没走过 
			for(int k=0;k<=n;k++) {//在哪
				if(((i&(1<<k-1))==0) | (k==j)) continue;
				dp[j][i]=min(dp[j][i],dp[k][i-(1<<j-1)]+a[j][k]);
			}
		}
	}
	for(int i=1;i<=n;i++) {
		ans=min(ans,dp[i][(1<<n)-1]);//将所有的都走过一遍了 
	}
	printf("%.2lf",ans);
	return 0;
}

状压最短路

有时候,使用 dp 具有后效性,此时要考虑与其他知识结合起来。

比如最短路算法。

例题:关灯问题II

写完 dp 后就发现,关灯的顺序不同,结果也不相同,就会涉及到重复转移,即后效性。

求问题的最优解还有最短路算法。每次进行关灯操作的代价均为 11。就可以把一种状态当作一个点。来最短路。

枚举进行的操作(类似边),利用单调性,看当前序列变成了什么(类似到达的点),如果是第一次,那就入队。

发现,总共有 2n2^n 个点,2n×m2^n\times m 条边。所以复杂度为 O(m2n+2n)O(m2^n+2^n)。在 n10,m100n\leq10,m\leq100 下可以通过。

#include<bits/stdc++.h>
using namespace std;
const int N = 11,M = 101;
int n,m;
int a[M][N];//选择了第 m 种,最后状态为 n
int ck(int k,int l) {//按照题意模拟
	for(int i=1;i<=n;i++) {
		if(a[k][i]==1 && (l&(1<<i-1)))
			l-=(1<<i-1);
		if(a[k][i]==-1 && ((l&(1<<i-1))==0))
			l+=(1<<i-1);
	}
	return l;
}
int vis[1<<N];
int main() {
	cin>>n>>m;
	for(int i=1;i<=m;i++) {
		for(int j=1;j<=n;j++) {
			cin>>a[i][j];
		}
	}
	memset(vis,-1,sizeof vis); vis[(1<<n)-1]=0;
	queue<int>q; q.push((1<<n)-1);
	while(!q.empty()) {
		int x=q.front(); q.pop();
		for(int j=1;j<=m;j++) {
			int l1=ck(j,x);
			if(vis[l1]==-1) vis[l1]=vis[x]+1, q.push(l1);
			if(l1==0) {
				cout<<vis[l1];
				return 0;
			}
		}
	}
	cout<<-1;
	return 0;
}

本文作者:cjrqwq

本文链接:https://www.cnblogs.com/yfzqwq/p/18492779

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   cjrqwq  阅读(5)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
展开
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.