状态压缩DP
通过将状态压缩为整数来达到优化转移的目的。 ——OI Wiki
题目状态 ----> 二进制(01串) ----> 每个二进制对应一个数值 ----> 数值代表着DP状态
例题
摸鱼
题目描述(此题并不是状压DP,是用来理解状态压缩的)
蜗蜗一共有 n\((2 ≤ n ≤ 20)\)天假期,在假期的第 i 天摸鱼他会得到 \(a_i\);\((1≤a_i≤100000)\)的快乐值。
如果蜗蜗每天都摸鱼的话,他会有愧疚感,所以蜗蜗制定了这么个计划:对于每一天,蜗蜗都有一
个列表,如果蜗蜗在列表中的每一天都在摸鱼的话,这一天蜗蜗就不能摸鱼。现在请问蜗蜗如何摸
鱼,使得他能获得的快乐值总和最大?请求出快乐值总和最大是多少。
输入
4 // 4天假期
1 2 3 4 // 每天摸鱼的快乐值
0 // 第i天的计划里有0天
1 1 // 第i+1天的计划里有1天,这天是第一天
1 2
2 2 3
输出
8
思路(状态压缩+暴力)
直接暴力做
假设有 n 天假期,则 n 天的摸鱼情况可以用 n 位的二进制表示,所以情况就是从 [0 .... 0] -> [1....1],0 表示没有摸鱼,1 表示这天摸鱼了。
除了假期,每一天的计划也可以用二进制表示,如何判断一个状态 i 是否可行?如果 i 在 j 天摸鱼了,就去看 j 天的计划,如果
\((i\&s[j] = s[j])\) 则不行。
代码
#include <bits/stdc++.h>typedef std::pair<int, int> pii;
#define INF 0x3f3f3f3f
#define MOD 998244353
using i64 = long long;
const int N = 1e5+5;void solve(){int n, ans = 0;std::cin >> n;std::vector<int> v(n+1), b(n+1), l(n+1), s(n+1);for (int i = 1; i <= n; i++) std::cin >> v[i];for (int i = 1; i <= n; i++){std::cin >> l[i];for (int j = 1; j <= l[i]; j++){int x;std::cin >> x;s[i] |= (1 << (x - 1));}}for (int i = 0; i < (1 << n); i++){for (int j = 1, k = i; j <= n; j++, k /= 2){b[j] = k & 1;}bool ok = true;for (int j = 1; j <= n && ok; j++){if (b[j] && l[j] && (i & s[j]) == s[j]){ok = false;}}if (!ok) continue;int res = 0;for (int j = 1; j <= n; j++){if (b[j]){res += v[j];}}ans = std::max(ans, res);}std::cout << ans << '\n';
}signed main()
{std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);int t = 1, i;for (i = 0; i < t; i++){solve();}return 0;
}
P10447 最短 Hamilton 路径
https://www.luogu.com.cn/problem/P10447
思路
根据题意,假设一共 n 个城市,则哪些城市去了,哪些城市没去很明显可以用二进制表示。
状态定义:\(f[i][j]\):表示在 i 这个状态下,当前在 j 城市的最小花费
初始化:\(f[1][0] = 0\),解释:第一个参数表示 0 这个城市去了,其他城市都没去,且当前在 0 城市,所以自然还没有产生花费
答案:\(f[(1<<n)-1][n-1]\),解释:所以城市都去了的状态是\((1<<n)-1\),当前在 n-1 这座城市
状态转移:\(f[i][j] = \min(f[i][j], f[1\wedge(1<<j)][k]+a[k][j])\),解释:j 城市去过,并且当前在 j 城市的DP值,一定是从 j 城市没有去过,现在刚好可以到达 j 城市的DP值中转移过来的(当前去的每一个城市都可以一步到达 j 城市)
这个转移办法是:思考目前的DP值是怎么来的
代码
#include <bits/stdc++.h>typedef std::pair<int, int> pii;
#define INF 0x3f3f3f3f
#define MOD 998244353
using i64 = long long;
const int N = 1e5+5;void solve(){int n; std::cin >> n;std::vector a(n, std::vector<int>(n, 0));for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){std::cin >> a[i][j];}}std::vector f(1 << 20, std::vector<i64>(n, INT_MAX));f[1][0] = 0;for (int i = 1; i < (1 << n); i++){for (int j = 0; j < n; j++){if (i >> j & 1){for (int k = 0; k < n; k++) if ((i^1 << j) >> k & 1){f[i][j] = std::min(f[i][j], f[i^1<<j][k]+a[k][j]);}}}}std::cout << f[(1<<n)-1][n-1];}signed main()
{std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);int t = 1, i;for (i = 0; i < t; i++){solve();}return 0;
}
思路二
除了状态转移不一样之外,其他都是一样的
状态转移:\(f[i+(1<<k)][k] = \min(f[i+(1<<k)][k], f[i][j] + a[j][k])\),解释:如果\(f[i][j]\)这个DP值是有意义的,则它一定可以转移到它没有到过的城市
转移方法:根据已经有的DP值转移出新的DP值
代码二
#include <bits/stdc++.h>typedef std::pair<int, int> pii;
#define INF 0x3f3f3f3f
#define MOD 998244353
using i64 = long long;
const int N = 1e5+5;void solve(){int n; std::cin >> n;std::vector a(n, std::vector<int>(n, 0));for (int i = 0; i < n; i++){for (int j = 0; j < n; j++){std::cin >> a[i][j];}}std::vector f(1 << 20, std::vector<i64>(n, INT_MAX));for (int i = 0; i < n; i++){f[0][i] = 0;}f[1][0] = 0;for (int i = 1; i < (1 << n); i++){for (int j = 0; j < n; j++){if (f[i][j] < INT_MAX){for (int k = 0; k < n; k++){if (!(i & (1 << k))){f[i+(1 << k)][k] = std::min(f[i+(1 << k)][k], f[i][j] + a[j][k]);}}}}}std::cout << f[(1<<n)-1][n-1];}signed main()
{std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);int t = 1, i;for (i = 0; i < t; i++){solve();}return 0;
}