题目描述
题解
这道题真的毒瘤…
首先观察到列的范围很小,这启示我们使用状态压缩算法。
我们可以预处理出所有横排不互相攻击的数字,显然这些数只有一百来个。用b数组存储。
我们观察到一行是否可以防止炮兵和上两行来决定,我们设 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示第 i i i行的状态为 b j b_j bj?,第 i ? 1 i-1 i?1行状态为 b k b_k bk?可以放置的最多炮兵。
因此我们设令一个状态为l,有一个显然的状态转移方程是: f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k ] , f [ i ? 1 ] [ k ] [ l ] + c o u n t ( j ) ) f[i][j][k]=max(f[i][j][k],f[i-1][k][l]+count(j)) f[i][j][k]=max(f[i][j][k],f[i?1][k][l]+count(j))
此时,我们需要考虑这个状态转移方程的限制条件:
- 显然,为了防止互相攻击,必须要满足第 b [ i ] & b [ i ? 1 ] = ? 0 b[i]\&b[i-1]=\not 0 b[i]&b[i?1]=??0和 b [ i ] & b [ i ? 2 ] = ? 0 b[i]\&b[i-2]=\not 0 b[i]&b[i?2]=??0.
- 为了让都在平原上,需满足 a [ i ] ∣ p = a [ i ] a[i]|p=a[i] a[i]∣p=a[i]. p ∈ { j , k } . p∈\{j,k\}. p∈{ j,k}.
- 然后我们让 f [ 0 ] [ 1 ] [ 1 ] = 0 f[0][1][1]=0 f[0][1][1]=0(若 b [ 1 ] = 0 b[1]=0 b[1]=0),其余为负无穷。还有, c o u n t ( i ) count(i) count(i)表示数字i的二进制1的个数。
代码如下:
#include <bits/stdc++.h>using namespace std;
const int N = 200;int n, m, ans = 0, s = 0;
int a[N], f[N][N][N], b[N], c[N];bool check(int x)
{
int t[20] = {
};for (int i=m-1;i>=0;--i) t[i+1] = x >> i & 1;for (int i=3;i<=m;++i) if (t[i]+t[i-1]+t[i-2] > 1) return 0;return 1;
}bool valid(int x,int y) {
return (x | a[y]) == a[y];
}bool safe(int x,int y,int z) {
return (b[x] & b[y]) == 0 && (b[y] & b[z]) == 0 && (b[x] &b[z]) == 0;
}int count(int x) {
int cnt = 0;while (x > 0) cnt += x&1, x >>= 1;return cnt;
}int main(void)
{
freopen("input.in","r",stdin);freopen("output.out","w",stdout);cin >> n >> m;for (int i=1;i<=n;++i){
int s = 0;for (int j=1;j<=m;++j){
char c;cin >> c;if (c == 'P') s = s<<1|1;else s = s<<1;}a[i] = s;}for (int i=0;i<1<<m;++i) if (check(i)) b[++s] = i, c[s] = count(i);memset(f,-30,sizeof f);f[0][1][1] = 0;for (int i=1;i<=n;++i)for (int j=1;j<=s;++j) if (valid(b[j],i))for (int k=1;k<=s;++k) if (valid(b[k],i-1) && (b[j]&b[k]) == 0)for (int l=1;l<=s;++l) if ((b[j]&b[l]) == 0) f[i][j][k] = max(f[i][j][k],f[i-1][k][l]+c[j]), ans = max(ans,f[i][j][k]);cout << ans << endl;return 0;
}