P2704 [NOI2001]炮兵阵地
P2704 [NOI2001]炮兵阵地
状压DP。
这个炮可以打到上面两行,这个点卡了我很久,我一开始就压一行的状态,发现会无线套娃:当前行可能会打到上两行,你还不能只判断当前行与上一行是否匹配,还得判断上两行;判了上一行还要判断上一行的上两行。。。。
为了解决这个问题,我们可以直接把两行的状态放到一个数组里,这样问题就解决了。
转移方程:\(dp[i][F][G] = max(calc(F) + dp[i - 1][G][LG])\)。\(F\)为当前行的状态,\(G\)为上一行的状态,\(LG\)为上两行的状态, \(calc(F)\)是计算当前行的炮兵数。
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 105, M = 2000;
int n, m, ans;
int num[N], c[N][M], S[N][M], mat[N][N], dp[N][M][M];
int judge(int x, int s) {
if((s << 1) & s) return 0;
if((s << 2) & s) return 0;
if((s >> 1) & s) return 0;
if((s >> 2) & s) return 0;
for(int i = 0;i < m; i++) {
int tmp = (s >> i) & 1;
if(mat[x][m - i] == 0 && tmp == 1) return 0;
}
return 1;
}
void make_pre_S() {
for(int i = 1;i <= n; i++)
for(int j = 0;j <= (1 << m) - 1; j++)
if(judge(i, j)) S[i][++num[i]] = j;
}
int calc(int s) {
int res = 0;
for(int i = 0;i < m; i++)
if((s >> i) & 1) res++;
return res;
}
int match(int s1, int s2) {
if(s1 & s2) return 0;
return 1;
}
int main() {
n = read(); m = read();
for(int i = 1;i <= n; i++) {
string ch; cin >> ch;
for(int j = 1;j <= m; j++)
if(ch[j - 1] == 'P') mat[i][j] = 1;
}
make_pre_S(); // 预处理出所有合法状态
num[0] = 1;
for(int i = 1;i <= num[1]; i++)
dp[1][S[1][i]][0] = calc(S[1][i]);
for(int i = 2;i <= n; i++) {
for(int s1 = 1;s1 <= num[i]; s1++) {
int F = S[i][s1];
for(int s2 = 1;s2 <= num[i - 1]; s2++) {
int G = S[i - 1][s2];
for(int s3 = 1; s3 <= num[i - 2]; s3++) {
int LG = S[i - 2][s3];
if(match(F, G) && match(F, LG) && match(G, LG)) {
dp[i][F][G] = max(dp[i][F][G], calc(F) + dp[i - 1][G][LG]);
}
}
}
}
}
for(int s1 = 1;s1 <= num[n]; s1++) {
int F = S[n][s1];
for(int s2 = 1; s2 <= num[n - 1]; s2++) {
int G = S[n - 1][s2];
ans = max(ans, dp[n][F][G]);
}
}
printf("%d", ans);
return 0;
}