状压DP TSP(状态压缩动态规划)

4 篇文章 0 订阅

压缩状态

把每种状态做离散化标记.
比如一个灯位于(i,j),灯亮的话light[ i ][ j ]=1;不亮就是0. 这个东西选是1不选是0. 这个点走过就是1没走过就是0等等……
所以我们把每种状态都可以哈希为一个十进制整数,这个整数根据每个点状态种类的数量不同可以对应转化为二进制、三进制、四进制等等来表示一个状态
比如二进制:1001,转化为十进制为9,那么状态[ 9 ]就代表着选四号位,不选三号位,不选二号位,选一号位这一种状态
再假设一个人只有三种状态,吃饭 0,睡觉 1,打东东 2,那么三进制:2101 转化为十进制64,那么状态[ 64 ]就代表着第四个人打东东,第三个人睡觉,第二个人吃饭,第一个人睡觉这样的状态

二进制操作

节选自传送门

’&’符号,x&y,会将两个十进制数在二进制下进行与运算(都1为1,其余为0) 然后返回其十进制下的值。例如 3(11)&2(10)=2(10)。
’|’符号,x|y,会将两个十进制数在二进制下进行或运算(都0为0,其余为1) 然后返回其十进制下的值。例如3(11)|2(10)=3(11)。
’^ ’符号,x ^ y,会将两个十进制数在二进制下进行异或运算(不同为1,其余 为0)然后返回其十进制下的值。例如3(11)^2(10)=1(01)。
’~ ’符号,~ x,按位取反。例如~101=010。
’<<’符号,左移操作,x<<2,将x在二进制下的每一位向左移动两位,最右边用0填充,x<<2相当于让x乘以4。 ’>>’符号,是右移操作,x>>1相当于给x/2,去掉x二进制下的最右一位.

1.判断一个数字x二进制下第i位是不是等于1。(最低第1位)
方法:if(((1<<(i−1))&x)>0) 将1左移i-1位,相当于制造了一个只有第i位 上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0, 说明x第i位上是1,反之则是0。

2.将一个数字x二进制下第i位更改成1。
方法:x=x|(1<<(i−1)) 证明方法与1类似。

3.将一个数字x二进制下第i位更改成0。
方法:x=x&~(1<<(i−1))

4.把一个数字二进制下最靠右的第一个1去掉。
方法:x=x&(x−1)

例题

我们先给出例题T140309 布阵
给定一个n*m的棋盘,相邻两格不能同时放棋子,求有多少种放法n,m<=12
输入
2 3
输出
17所有的放法

理解一个“DP”所需要的定义

数组状态定义:dp[ i ][ p ] 表示 第i行 状态为p的方案数
状态转移:只与其上一行的状态有关 ,dp[ i ][ p ] = 对所有满足 p&q==0 的dp[ i-1 ][ q ] 求和
初始化:dp[ 0 ][ 0 ] = 1
最终答案 对于最后一行中符合p&(p>>1)的 dp[ n ][ p ] 求和

时间复杂度分析和优化

这样子时间复杂度很高,在做每一行的状态是2^n , 一共n行 , 转移时固定p遍历q,结果是n* 4 ^ n
这样子我们就对他进行优化,提前枚举并记录可以存在的状态

int num[inf];
int cnt=0;
int limit = 1<<m  //m是列数
for(int i=0;i<limit;i++)
	if((i&(i>>1))==0)
		num[++cnt]=i;

据说num数组和斐波那契有关

上述全部是建立在状态不能1相邻的情况下,若题意改变则需要变换,同时,因为过高的复杂度,数据量很小可考虑状压DP

简略实现代码

dp[0][0]=1;
for(int i=1;i<=n;i++)
{
	for(int j=1;j<=cnt;j++)
	{
		int p=num[j];
		for(int k=1;k<=cnt;k++)
		{
			int q=num[k];
			if((p&q)==0)
				dp[i][p]=(dp[i][p],dp[i-1][q])%MOD;
		}
	}
}

例题 AcWing 91.最短Hamilton路径 TSP

题目大意:给定一张完全图,求一条从0开始到n-1结束的,经过所有点恰好一次的最短路。1<=n<=20

状态就是已经走过了哪些点在这里插入图片描述

理解一个“DP”所需要的定义

数组状态定义:dp[ i ][ p ] 表示当前状态p已经经过了,从0走到i的路径和最小值
状态转移:松弛(迪科斯彻算法)每条边
初始化:dp[ 0 ][ 1 ] = 0,其他为inf
最终答案 dp[ n-1 ][ (1<<n)-1 ]

简略实现代码

cin>>n;
for(int i=0;i<n;i++)
	for(int j=0;j<n;j++)
		cin>>G[i][j];
dp[0][1]=0;
int limit=1<<n;
for(int i=1;i<limit;i++)//遍历状态
	for(int j=0;j<n;j++) // 遍历起点
		if((i>>j)&1)// 起点在状态内
			for(int k=0;k<n;k++)// 遍历终点 
				if(((i>>k)&1)==0)  // 终点不在状态内 
					dp[k][i|1<<k]=min(dp[k][i|1<<k],G[k][j]+dp[j][i])  // i|1<<k 意思是把状态的k位改成1
cout<<dp[n-1][limit-1]<<endl;
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值