初识状态压缩DP
状态压缩DP
通过将状态压缩为整数来达到优化转移的目的。 ——OI Wiki
题目状态 ----> 二进制(01串) ----> 每个二进制对应一个数值 ----> 数值代表着DP状态
例题
摸鱼
题目描述(此题并不是状压DP,是用来理解状态压缩的)
蜗蜗一共有 n
如果蜗蜗每天都摸鱼的话,他会有愧疚感,所以蜗蜗制定了这么个计划:对于每一天,蜗蜗都有一
个列表,如果蜗蜗在列表中的每一天都在摸鱼的话,这一天蜗蜗就不能摸鱼。现在请问蜗蜗如何摸
鱼,使得他能获得的快乐值总和最大?请求出快乐值总和最大是多少。
输入
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 天的计划,如果
则不行。
代码
#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 路径
思路
根据题意,假设一共 n 个城市,则哪些城市去了,哪些城市没去很明显可以用二进制表示。
状态定义:
:表示在 i 这个状态下,当前在 j 城市的最小花费 初始化:
,解释:第一个参数表示 0 这个城市去了,其他城市都没去,且当前在 0 城市,所以自然还没有产生花费 答案:
,解释:所以城市都去了的状态是 ,当前在 n-1 这座城市 状态转移:
,解释: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; }
思路二
除了状态转移不一样之外,其他都是一样的
状态转移:
,解释:如果 这个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; }
消除
题目
桌面上有
等于
请问蜗蜗最少需要花费多少代价,能把 n 个方块都消除掉?
输入
3 1 4 5
输出
2
思路
用二进制数表示方块的状态,1 表示删除,0 表示没有删除,则所有状态区间是
。 然后枚举每一个方块 j 有两种选择:1,单独删 j 这个方块,2,将 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; std::cin >> n; std::vector<int> v(n+1); for (int i = 1; i <= n; i++) std::cin >> v[i]; std::vector<int> f(1 << n, INF); f[0] = 0; for (int i = 0; i < (1 << n); i++){ for (int j = 1; j <= n; j++){ if (!(i & (1 << (j-1)))){ f[i+(1<<(j-1))] = std::min(f[i+(1<<(j-1))], f[i] + v[j]); for (int k = j + 1; k <= n; k++){ if (!(i & (1<<(k-1)))) f[i+(1<<(j-1))+(1<<(k-1))] = std::min(f[i+(1<<(j-1))+(1<<(k-1))], f[i]+(v[j]^v[k])); } break; } } } std::cout << f[(1<<n)-1]<<'\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; }
麦当劳
题目
喜欢吃麦当劳的蜗蜗要在学校呆
得
最多只能有一半的天数吃麦当劳。请问蜗蜗在这 n 天中最多可以得到多少快乐值?
思路
直接用二进制表示第 i 天吃不吃的状态显然是不行的,因为 n 的范围太大了,但是我们注意到 m 的范围非常小,可以思考一下有没有我们可以利用的东西。我们能想到,对于第 i 天,我们只需要考虑它前 m 天的情况就行了,所以。
状态定义:
:第 i 天,前 m 天的状态是 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, m; std::cin >> n >> m; std::vector<int> a(n+1), b(1<<m), f(1<<m, -1), v(1 << m, -1); for (int i = 1; i <= n; i++) std::cin >> a[i]; for (int i = 0; i < 1<<m; i++){ b[i] = 0; int cnt = 0; for (int j = 1; j <= m; j++){ if (i & (1<<(j-1))) cnt++; } if (cnt <= m / 2){ b[i] = 1; } } f[0] = 0; for (int i = 1; i <= n; i++){ std::fill(v.begin(), v.end(), -1); for (int j = 0; j < 1 << m; j++){ if (f[j] >= 0){ v[j / 2] = std::max(v[j / 2], f[j]); if (b[j / 2 + (1 << (m - 1))]){ v[j / 2 + (1 << (m - 1))] = std::max(v[j / 2 + (1 << (m - 1))], f[j] + a[i]); } } } std::copy(v.begin(), v.end(), f.begin()); } int ans = 0; for (int i = 0; i < 1 << m; i++){ ans = std::max(ans, f[i]); } 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; }
本文作者:califeee
本文链接:https://www.cnblogs.com/califeee/p/18650082
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步