题目描述
题解
这道题真的毒瘤…
首先观察到列的范围很小,这启示我们使用状态压缩算法。
我们可以预处理出所有横排不互相攻击的数字,显然这些数只有一百来个。用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;
}