状压DP往往有套路,问题划分为阶段,并且阶段不是“一个”,而是“一群”,在上一个阶段到下一个阶段时,会增加一堆数据而不是一个,然后这一堆数据可以压缩成一个二进制,表示哪些有哪些没有
阶段的划分要“整齐”,比如这题是一行一行来的,维度的设计要考虑影响关系,因此设f[i][j][k]表示阶段进行到第i行(前i行)最后一行状态为k,最后一行的上一行状态为j的最优解
另一个套路就是预处理,几乎状态题都要预处理出“可转移”的各种情况
大致形式是:i, j 表示两个二进制串 则g[i][j] 表示i, j若处于相邻阶段,或者说仅仅是“若i状态转移到j状态”,会消耗或为答案贡献什么
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int INF = (1<<30);
int gra[110][12],n,m,f[110][100][100],s[110],tot,ans,temte[12],temtot;
void print(int k);
void initialization() {
for(int i=0; i<1<<m; i++) {
int pre=-1;
bool flg = true;
for(int j=0; j<m; j++)
if((i>>j) & 1) {
if(pre>=0 && j-pre<3) { //注意pre>=0,因为最右边第一位可能就是1
flg = false;
break;
}
pre = j;
}
if(flg)
s[++tot] = i;
}
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
for(int l=1; l<=m; l++)
f[i][j][l] = -INF;
}
bool dep(int p, int k) {
int tem = s[k];
for(int i=0; i<m; i++)
if(!gra[p][m-i] && (tem >> i & 1))
return false;
return true;
}
void print(int k) {//调试用,输出二进制
k = s[k];
temtot = 0;
for(int i=0; i<m; i++)
temte[++temtot] = (k >> i) & 1;
for(int i=temtot; i>=1; i--)
cout <<temte[i];
cout << endl;
}
int count(int k) {
int tem = s[k];
int sum = 0;
for(int i=0; i<m; i++)
if(tem>>i&1) sum++;
return sum;
}
int main() {
scanf("%d %d", &n, &m);
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
char ch;
cin >> ch;
if(ch == 'P') gra[i][j] = 1;
}
}
initialization();
f[0][0][0] = 0;
for(int i=1; i<=n; i++)
for(int j=1; j<=tot; j++)
for(int k=1; k<=tot; k++) {
int temp = -INF;
if(!(s[j] & s[k]) && dep(i,j) && dep(i-1,k) ) {
for(int l=1; l<=tot; l++)
if(!(s[j] & s[l]))
temp = max(temp, f[i-1][k][l]);
f[i][j][k] = temp + count(j);
}
}
for(int i=1; i<=tot; i++)
for(int j=1; j<=tot; j++)
if(!(s[i] & s[j]) && dep(n,i) && dep(n-1,j))
ans = max(ans, f[n][i][j]);
printf("%d\n", ans);
return 0;
}