状态机DP
- 与背包dp不同,处在每个位置或时刻,可以有多种状态
1049. 大盗阿福
- 对于每个状态,有选择和不选两种状态
- 对于当前位置,如果选择,根据题目要求,前一个位置必须不能选
- 如果不选当前位置,可以从前一个状态中转移过来,也可以从前两个状态转移过来
1057. 股票买卖 IV
初始化:
- (dp[i][j][1])初始时均为不合法,设置为负无穷
- (dp[i][j][0])设置为零
状态表示:
- (dp[i][j][0]): 在前(i)天内,最多执行(k)此交易,当前手中无存货时,所得的最大利益
- (dp[i][j][1]): 在前(i)天内,最多执行(k)此交易,当前手中有存货时,所得的最大利益
状态计算:
- (dp[i][j][0])
- 前一天手中无货:(dp[i - 1][j][0])
- 前一天买入:(dp[i - 1][j][1] - w[i])
- (dp[i][j][1])
- 前一天手中有货:(dp[i - 1][j][1])
- 前一天买入:(dp[i - 1][j - 1][0] - w[i])
结果:求最多交易(k)次,所获得的最大利益
1058. 股票买卖 V
状态表示:
- (dp[i][0]): 在前(i)天内, 手中有存货
- (dp[i][1]):在前(i)天内,手中无存货的第一天
- (dp[i][2]):在前(i)天内,手中无存货了好几天(多于一天)
状态计算:
- (dp[i][0])
- (dp[i - 1][2] - w[i]):手中无存货了好几天,买入
- (dp[i][0]): 手中无存货
-
(dp[i][1])
(dp[i - 1][0] + w[i]):前一天手中有存货,卖掉了 -
(dp[i][2])
- (dp[i - 1][1]):前一天是没有存货的第一天
- (dp[i - 1][2]):前一天也是没存货好几天了
状态压缩DP
- 基于连通性的DP(棋盘类)
291. 蒙德里安的梦想
-
如果横放的长方形摆放好了,那么竖放的长方形的摆放的方案数就确定了,因此只需考虑横放的数量即可
-
(dp[i][j])表示第(i)列,上一列中那些行伸出来的小方格状态数,伸出来记为1,否则值为零,由此产生的二进制数的十进制表示
-
两个转移条件:
- (i) 列和 (i - 1)列同一行不同时捅出来:((j & k) == 0)
- 本列伸出来的状态(j)和上列捅出来的状态(k)求或,得到上列是否为偶数空行状态,如果是奇数空行不转移:
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 12, M = 1 << N;
int n, m;
long long dp[N][M];
bool st[M];
vector<int> states[M];
int main() {
while(cin >> n >> m , n || m) {
for(int i = 0; i < 1 << n; i++) {
st[i] = true;
int cnt = 0;
for(int j = 0; j < n; j++) {
if(i >> j & 1) {
if(cnt & 1) {
st[i] = false;
break;
}
}
else cnt++;
}
if(cnt & 1) st[i] = false;
}
// cout << endl;
for(int i = 0; i < 1 << n; i++) {
states[i].clear();
for(int j = 0; j < 1 << n; j++) {
if((i & j) == 0 && st[i | j])
states[i].push_back(j);
}
}
memset(dp, 0, sizeof dp);
dp[0][0] = 1;
for(int i = 1; i <= m; i++) {
for(int j = 0; j < 1 << n; j++) {
for(auto k : states[j]) {
dp[i][j] += dp[i - 1][k];
}
}
}
cout << dp[m][0] << endl;
}
return 0;
}
1064. 小国王
- 状态表示:(dp[i][j][s]) 表示当前枚举到第(i)行,已经用了(k)个棋子,前一行棋子摆放的状态是s, 如果摆放记为1否则记为0,所形成的的十进制数
- 合法方案:
- 第(i - 1)行内部不能有两个1相邻
- 第(i - 1)行和第(i)行之间不能互相攻击到
- 状态计算:
已经摆完前(i)排,第(i)排状态为(a),第(i - 1)排状态为(b),已经摆了(j)个国王的所有方案。
已经摆完前(i - 1)排,第(i - 1)排状态为(b),已经摆了(j - count(a))个国王的所有方案, (dp[i - 1][j - count(a)][b])
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 12, M = 1 << 10, K = 110;
LL dp[N][K][M];
int cnt[M], n, m;
vector<int> h[M];
vector<int> states;
// 判断每行中是否中所有合法的状态:不含有相邻的不为1的数
bool check(int state) {
for(int i = 0; i < n; i++)
if((state >> i & 1) && (state >> i + 1 & 1))
return false;
return true;
}
// 计算该状态在一行中放置的棋子数量
int count(int state) {
int res = 0;
for(int i = 0; i < n; i++) res += (state >> i & 1);
return res;
}
int main() {
cin >> n >> m;
// 预处理各种可行的状态,存入states数组
for(int i = 0; i < 1 << n; i++) {
if(check(i)) {
states.push_back(i);
cnt[i] = count(i);
}
}
// 枚举可行的状态,将两者可以作为上下行的存入h数组
for(int i = 0; i < states.size(); i++) {
for(int j = 0; j < states.size(); j++) {
int a = states[i], b = states[j];
if((a & b) == 0 && check(a | b))
h[i].push_back(j);
}
}
// 什么都没放的方案数为1
dp[0][0][0] = 1;
// 枚举每行
for(int i = 1; i <= n + 1; i ++) {
// 枚举所有的棋子
for(int j = 0; j <= m; j++) {
// 遍历所有的可行的状态
for(int a = 0; a < states.size(); a++) {
for(auto b : h[a]) {
int c = cnt[states[a]];
if(j >= c)
dp[i][j][a] += dp[i - 1][j - c][b];
}
}
}
}
cout << dp[n + 1][m][0];
return 0;
}
292. 炮兵阵地
- 状态表示:(dp[i][j][k]) 表示枚举到第(i)行,其上一行摆放的状态是(j), 上两行的状态为(k)
- 状态计算:如果条件合法,(dp[i][j][k] = max(dp[i - 1][j][l] + cnt[states[l]], dp[i][j][k]))
- 判断条件:
- 当前为平地
- 当前行、当前行的上一层、当前行的上两层,之间不能有交集
#include <iostream>
#include <vector>
using namespace std;
const int N = 110, M = 12;
int g[N], cnt[1 << M];
int n, m;
int dp[N][1 << M][1 << M];
vector<int> states;
vector<int> h[1 << M];
// 行与行之间不能有交集
bool check(int state) {
for(int i = 0; i < m; i++)
if((state >> i & 1) && ((state >> i + 1 & 1) || (state >> i + 2 & 1)))
return false;
return true;
}
int count(int state) {
int res = 0;
for(int i = 0; i < m; i++) res += (state >> i & 1);
return res;
}
int main() {
cin >> n >> m;
for(int i = 1; i <= n; i++) {
for(int j = 0; j < m; j++) {
char c; cin >> c;
// 此处为山地,不能摆放炮团,标记为1
g[i] += (c == 'H') << j;
}
}
for(int i = 0; i < 1 << m; i++)
if(check(i)) {
states.push_back(i);
cnt[i] = count(i);
}
for(int i = 1; i <= n + 2; i++)
// 枚举i行状态
for(int j = 0; j < states.size(); j++)
// i - 1状态
for(int k = 0; k < states.size(); k++)
// i - 2状态
for(int l = 0; l < states.size(); l++) {
int curr = states[j], r1 = states[k], r2 = states[l];
if((r1 & r2) | (r1 & curr) | (curr & r2)) continue;
if((g[i] & curr) | (g[i - 1] & r1)) continue;
dp[i & 1][j][k] = max(dp[i & 1][j][k], dp[i - 1 & 1][k][l] + cnt[curr]);
}
cout << dp[n + 2 & 1][0][0];
return 0;
}
- 集合类状态压缩DP
91. 最短Hamilton路径
- 将途径的点的状态压缩,途径的点记为1,未途径为0
- (dp[i][j]) 表示到达(j)这个点时,最短的路径长度
- 状态计算:(dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + a[k][j])):
找到更短的路径,从(k)转移过来,应该不途径第(j)个点,把j从经过的点集中去掉再加上从(k)到(j)的距离
// 初始化
memset(dp, 0x3f, sizeof dp);
// 开始,途径第零个点
dp[1][0] = 0;
for(int i = 0; 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)
dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + a[k][j]);
}
}
cout << dp[(1 << n) - 1][n - 1];