状态压缩动态规划(上)

目录:

一:复习:二进制与位运算

二:二进制枚举子集(附李白喝酒,附凑X)

三:状态压缩动态规划

1)一维

(附传递物品,附旅行商问题及改编后闭包处理)

一:
 复习:
 二进制:二进制转十进制,十进制转二进制,二进制加减法
 位运算:&,|,^,<<,>>;(a&1->a%2==1)

二:
二进制枚举子集:
用二进制一位表示表示集合对应某一元素的选取状态,1选取,0为选取;


李白喝酒:
逢店加一倍,遇花喝一斗
起初酒两斗,遇店5,遇花10,最后一次遇花,恰好喝完
 

#include<iostream>
using namespace std;
int main() {
	int ans = 0;
	for (int i = 0; i < (1 << 14); i++) {       //(1<<14)=(2^14)=14个1+1
		int top_1 = 0;
		int top_0 = 0;
		int num = 2;
		for (int j = 0; j < 14; j++) {
			if (i & (1 << j)) {        //判断二进制i从右数第j+1位是否是1
				top_1++;
				num *= 2;
			}
			else {
				top_0++;
				num -= 1;
			}
		}
		if (top_1 == 5 && top_0 == 9 && num == 1) {
			ans++;
		}
	}
	cout << ans << endl;
}


n个互不相同正整数无重复的选取任意个数通过加法凑出X,求方案数
 

#include<iostream>
using namespace std;
int main()
{
	int n, a[30], X;
	int ans = 0;
	cin >> n;
	cin >> X;
	for (int i = 0; i < n; i++)
		cin >> a[i];
	for (int i = 1; i < (1 << n); i++)
	{
		int  t = 0;
		for (int k = 0; k < n; k++) {
			if (i & (1 << k))
				t += a[k];
		}
		if (t == X)
		{
			for (int k = 0; k < n; k++)
				if (i & (1 << k)) cout << a[k] << " ";
			cout << endl;
			ans++;
		}
	}
	cout << ans;
	return 0;
}

三.
状态压缩动态规划(状压DP)

1)一维
通过具体例题了解:
n个人做传递物品的游戏,编号1到n,
游戏规则:开始物品在任意一人手里,可把物品传给其他任意一位,下一个人传给
未接过物品的任意一人,物品只能经过同一人一次,且传递过程都有一个代价,
不同人传给不同人代价值之间没有联系
求当物品经过n个人之后总代价
 低位存储编号小,高位编号大
 开始物品可在任意一人手中,即dp[1<<i][i]=0(可得到n个1状态),其余状态置于无穷大
 假如j传给k,k必须不在集合i里,即需满足(i&(1<<k))==0
状态转移方程:
dp[i | (1 << k)][k] = min(dp[i | (1 << k)][k], dp[i][j] + a[j][k]);
a[j][k]即从j到k的代价
 输入:
 3
 -1 2 4
 3 -1 5
 4 4 -1
 输出
 6

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
int a[20][20];
int dp[1 << 16][20];
int main()
{
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			cin >> a[i][j];
		}
	}
	memset(dp, 0x3f, sizeof(dp));
	for (int i = 0; i < n; i++) {
		dp[1 << i][i] = 0;
	}
	for (int i = 0; i < (1 << n); i++) {
		for (int j = 0; j < n; j++) {   ///判断作用********
			if (i & (1 << j)) {
				for (int k = 0; k < n; k++) {
					if (!(i & (1 << k))) {
						dp[i | 1 << k][k] = min(dp[i | 1 << k][k],dp[i][j] + a[j][k]);
					}
				}
			}
		}
	}
	int ans = INF;
	for (int i = 0; i < n; i++) {
		ans = min(ans, dp[(1 << n) - 1][i]);  //枚举初始状态物品在i手中最小代价
	}
	cout << ans << endl;
	return 0;
}


旅行商问题——TSP问题
假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是
每个城市只能拜访一次,而且最后要回到原来出发的城市,求路径路程最小值
路径为环,可任一点为起点和终点

总体思想为倒退,与传递物品恰好相反
输入:
4
0 2 6 5
2 0 4 4
6 4 0 2
5 4 2 0
输出:
13        

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
int dist[20][20];
int dp[1 << 16][20];
int main()
{
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			cin >> dist[i][j];
			if (dist[i][j] == -1)
				dist[i][j] = INF;
		}
	}
	memset(dp, 0x3f, sizeof(dp));
	dp[1][0] = 0;                   //以城市0为起点终点,状态为1;
	for (int s = 0; s < (1 << n); s++) {
		for (int i = 0; i < n; i++) {     //判断作用
			if (s & (1 << i)) {
				for (int j = 0; j < n; j++) {
					if (j != i && (s & (1 << j))) {  ///s ^ 1相当于集合减法
						dp[s][i] = min(dp[s][i], dp[s ^ 1 << i][j] + dist[j][i]);
						/*dp[s][i] = min(dp[s][i], dp[s - (1 << i)][j] + dist[j][i]);*/
					}
				}
			}
		}
	}
	int ans = INF;
	for (int i = 0; i < n; i++) {
		ans = min(ans, dp[(1 << n) - 1][i] + dist[i][0]);
	}//枚举所有倒数第二城市状态并返回城市0
	if (ans == INF)
		cout << -1 << endl;
	else
		cout << ans << endl;
	return 0;
}

去除每个城市只能去一次限制
只需Floyd算法预处理每两个城市最短路——这一步称为闭包传递
 输入:
3
-1 1 10
1 -1 2
10 2 -1
输出:
6

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int INF = 0x3f3f3f3f;
int dist[20][20];
int dp[1 << 16][20];
int main()
{
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			cin >> dist[i][j];
			if (dist[i][j] == -1)
				dist[i][j] = INF;
		}
	}
	for (int k = 0; k < n; k++) {
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
			}
		}
	}
	memset(dp, 0x3f, sizeof(dp));
	dp[1][0] = 0;                   //以城市0为起点终点,状态为1;
	for (int s = 0; s < (1 << n); s++) {
		for (int i = 0; i < n; i++) {     //判断作用
			if (s & (1 << i)) {
				for (int j = 0; j < n; j++) {
					if (j != i && (s & (1 << j))) {  ///s ^ 1相当于集合减法
						dp[s][i] = min(dp[s][i], dp[s ^ 1 << i][j] + dist[j][i]);
						/*dp[s][i] = min(dp[s][i], dp[s - (1 << i)][j] + dist[j][i]);*/
					}
				}
			}
		}
	}
	int ans = INF;
	for (int i = 0; i < n; i++) {
		ans = min(ans, dp[(1 << n) - 1][i] + dist[i][0]);
	}//枚举所有倒数第二城市状态并返回城市0
	if (ans == INF)
		cout << -1 << endl;
	else
		cout << ans << endl;
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值