0. 前言
相关:
强相关:
1. 状压dp+棋盘式(基于连通性)
[状压dp] 玉米田(状压dp) 是高度为 1 的十字形,本题是 高度为 2 的十字形。 那么状态转移就和前 2 层有关。且本题是求最大值,而非方案数。
思路:
- 状态定义:
f[i][j][k]
:摆放完前i
行,且第i-1
行摆放状态是j
,第i
行摆放状态是k
的所有摆放方案的最大值
- 状态计算:
-
假设第
i
行状态是b
,第i-1
行状态是a
,第i-2
行状态是c
-
当
((a&b) | (a&c) | (b&c)) = 0
则说明纵向不会出现冲突 -
当
(g[i-1]&a | g[i]&b)=0
则说明炮兵在平地上。在此与的优先级高于或的优先级,不用加括号 -
状态转移方程:
f[i][j][k] = max(f[i][j][k], f[i-1][c][j]+cnt[c])
-
- 时间复杂度: n ∗ 2 m ∗ 2 m ∗ 2 m = O ( n 2 3 m ) = 100 ∗ 2 30 = 1 0 11 n*2^m*2^m*2^m=O(n2^{3m})=100*2^{30}=10^{11} n∗2m∗2m∗2m=O(n23m)=100∗230=1011 这个时间复杂度…但是里面合法状态很少…依旧可以过
滚动数组优化,不然空间直接爆炸。
重点还是在状态定义及状态计算上。这里状态定义记录了第 i
行状态为 k
、第 i-1
行状态为 j
。那么找到最后一个不一样的点就是第 i-2
行,所以就依据第 i-2
行进行状态划分即可。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
const int N = 11, M = 1 << 10;
int n, m;
int g[105];
vector<int> state;
int cnt[M];
int f[2][M][M]; // 使用滚动数组,后两维都已经100w了...
bool check(int x) {
for (int i = 0; i < m; ++i) // 针对列做状态压缩,同列三个不能有2个1
if ((x >> i & 1) && ((x >> i + 1 & 1) | (x >> i + 2 & 1)))
return false;
return true;
}
int count(int x) {
int res = 0;
for (int i = 0; i < m; ++i) res += x >> 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; // 拿int类型读入char,读入全是0
cin >> c;
if (c == 'H')
g[i] += 1 << j; // 二进制表示一行
}
for (int i = 0; i < 1 << m; ++i)
if (check(i)) {
state.push_back(i);
cnt[i] = count(i);
}
for (int i = 1; i <= n + 2; ++i)
for (int j = 0; j < state.size(); ++j) // j为第i-1行状态
for (int k = 0; k < state.size(); ++k) // k为第i行状态
for (int u = 0; u < state.size(); ++u) { // u为第i-2行状态
int a = state[j], b = state[k], c = state[u];
if ((a & b) | (b & c) | (a & c)) continue;
if (g[i - 1] & a | g[i] & b) continue;
f[i & 1][j][k] = max(f[i & 1][j][k], f[i - 1 & 1][u][j] + cnt[b]);
}
cout << f[n + 2 & 1][0][0] << endl;
// 遍历写法,修改第一个 for (int i = 1; i <= n; ++i)
/*
int res = 0;
for (int i = 0; i < state.size(); ++i)
for (int j = 0; j < state.size(); ++j)
res = max(res, f[n & 1][i][j]);
cout << res << endl;
*/
return 0;
}