目录:
一:复习:二进制与位运算
二:二进制枚举子集(附李白喝酒,附凑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;
}