2025-01-03 14:37阅读: 8评论: 0推荐: 0

初识状态压缩DP

状态压缩DP

通过将状态压缩为整数来达到优化转移的目的。 ——OI Wiki

题目状态 ----> 二进制(01串) ----> 每个二进制对应一个数值 ----> 数值代表着DP状态

例题

摸鱼

题目描述(此题并不是状压DP,是用来理解状态压缩的)

蜗蜗一共有 n(2n20)天假期,在假期的第 i 天摸鱼他会得到 ai;(1ai100000)的快乐值。
如果蜗蜗每天都摸鱼的话,他会有愧疚感,所以蜗蜗制定了这么个计划:对于每一天,蜗蜗都有一
个列表,如果蜗蜗在列表中的每一天都在摸鱼的话,这一天蜗蜗就不能摸鱼。现在请问蜗蜗如何摸
鱼,使得他能获得的快乐值总和最大?请求出快乐值总和最大是多少。

输入

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][n1],解释:所以城市都去了的状态是(1<<n)1,当前在 n-1 这座城市

状态转移:f[i][j]=min(f[i][j],f[1(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;
}

消除

题目

桌面上有n(2n20)个方块,蜗蜗想把它们都消除掉。每个方块有个权值,第 i 个方块的权值
等于ai(1a100000)。每一次消除蜗蜗有两种选择:1.选择一个还没有被消除的方块 i,付出
ai的代价把它消除;2.选择两个还没有被消除的方块i,j(ij),付出aiaj​ 的代价把它们消除;
请问蜗蜗最少需要花费多少代价,能把 n 个方块都消除掉?

输入

3
1 4 5

输出

2

思路

用二进制数表示方块的状态,1 表示删除,0 表示没有删除,则所有状态区间是[0,2m1]

然后枚举每一个方块 j 有两种选择:1,单独删 j 这个方块,2,将 j 和其他没有删的方块一起删除,时间复杂度O(n22m)

考虑到每一个方块到最后都是要删除的,先删除哪一个是不影响最后的结果的,所以遇到可删除的直接删除即可。

代码

#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(2n100000)天,如果第i天蜗蜗吃到了麦当劳,他可以获
ai(1ai10000点快乐值。然而蜗蜗不能吃太多麦当劳,在连续的m(2m8)天中,他
最多只能有一半的天数吃麦当劳。请问蜗蜗在这 n 天中最多可以得到多少快乐值?

思路

直接用二进制表示第 i 天吃不吃的状态显然是不行的,因为 n 的范围太大了,但是我们注意到 m 的范围非常小,可以思考一下有没有我们可以利用的东西。我们能想到,对于第 i 天,我们只需要考虑它前 m 天的情况就行了,所以。

状态定义:f[i][j]:第 i 天,前 m 天的状态是 j 的最多快乐值

初始值:f[0][0]=0

答案:max(jf[n][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 中国大陆许可协议进行许可。

posted @   califeee  阅读(8)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.