题目链接:POJ 1185 炮兵阵地
题目大意:
题解:
因为每一个炮台左右都是会互相攻击的,也就是说有些状态是不需要枚举的,例如\(0011\),再者如果要用\(0\)到\(1023\)来枚举的话,\(1024^3 \times 100\)的复杂度是不能接受的,所以我们需要通过预处理并装入\(sta\)数组来将其缩减,在缩减之后枚举数组中的数字(最多\(60\)个状态)即可。
对于状态\(x\),我们只需要右移一位和两位之后与原数按位与\((\&)\)判断是否为\(0\)即可判断其是否存在互相攻击的情况。
对于这道题来说我们没必要把图用字符数组存下来的,我们只关心图上“\(H\)”的位置是否会和我们枚举的状态冲突,所以可以把每一排也用二进制压缩的方式变成一个数字,整张图就会变成一个一维数组,然后可以非常方便的进行判断是否冲突。
在判断上下层是否冲突的时候即是在判断两个状态数有没有同一个位置都为\(1\),也就是说我们只需要按位与\((\&)\)看结果是否为\(0\)即可。
设\(dp[i][j][k]\)表示的是到第\(i\)层为止且第\(i\)层状态为\(sta[j]\),第\(i-1\)层状态为\(sta[k]\)的情况下可以摆放的炮兵部队的最大数量。
状态转移方程为:
#include <iostream>
using namespace std;
int dp[2][65][65], mat[110], n, m, cnt, sta[65], num[65], now, ans;
int count(int x) { // 计算x的二进制表示中1的个数
int k = 0;
while (x) {
k++;
x -= x & (-x);
}
return k;
}
void init() { // 初始化可行的状态
for (int i = 0; i < 1 << m; ++i) {
if (!(i & i >> 1) && !(i & i >> 2)) {
sta[++cnt] = i;
num[cnt] = count(i);
}
}
}
int main() {
cin >> n >> m;
if (!n || !m) {
cout << 0;
return 0;
}
init();
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < m; ++j) {
char ch;
cin >> ch;
if (ch == 'H') { // 将图压缩成二进制数
mat[i] |= 1 << j;
}
}
}
now = 0; // 滚动数组
for (int i = 1; i <= cnt; ++i) { // 预处理第一行
if (!(mat[1] & sta[i])) {
dp[now][i][0] = num[i];
ans = max(ans, dp[now][i][0]);
}
}
if (n == 1) {
cout << ans;
return 0;
}
now ^= 1;
for (int i = 1; i <= cnt; ++i) { // 预处理第二行
if (!(mat[2] & sta[i])) {
for (int j = 1; j <= cnt; ++j) {
if (!(sta[i] & sta[j])) {
dp[now][i][j] = dp[now ^ 1][j][0] + num[i];
ans = max(ans, dp[now][i][j]);
}
}
}
}
for (int t = 3; t <= n; ++t) {
now ^= 1;
for (int i = 1; i <= cnt; ++i) {
if (!(mat[t] & sta[i])) {
for (int j = 1; j <= cnt; ++j) {
if (!(mat[t - 1] & sta[j]) && !(sta[i] & sta[j])) {
for (int k = 1; k <= cnt; ++k) {
if (!(mat[t - 2] & sta[k]) && !(sta[i] & sta[k]) && !(sta[j] & sta[k])) {
dp[now][i][j] = max(dp[now][i][j], dp[now ^ 1][j][k] + num[i]);
ans = max(ans, dp[now][i][j]);
}
}
}
}
}
}
}
cout << ans;
return 0;
}