~~~~~ P2704 [NOI2001] 炮兵阵地 ~~~~~ 总题单链接
思路
~~~~~ 发现 m ≤ 10 m\leq 10 m≤10,考虑状压DP。
~~~~~ 发先同一行有限制,先预处理出满足同一行的限制的数字,最多只有 60 60 60 个。
~~~~~ 设 d p [ i ] [ k 2 ] [ k 3 ] dp[i][k2][k3] dp[i][k2][k3] 表示当前考虑了前 i i i 行,第 i − 1 i-1 i−1 行为 k 2 k2 k2,第 i i i 行为 k 3 k3 k3 的最大值。
~~~~~ 转移的时候分别枚举第 i − 2 , i − 1 , i i-2,i-1,i i−2,i−1,i 行的状态 k 1 , k 2 , k 3 k1,k2,k3 k1,k2,k3。如果合法则 d p [ i ] [ k 2 ] [ k 3 ] = m a x ( d p [ i ] [ k 2 ] [ k 3 ] , d p [ i − 1 ] [ k 1 ] [ k 2 ] + ( k 3 中 1 的个数 ) ) dp[i][k2][k3]=max(dp[i][k2][k3],dp[i-1][k1][k2]+(k3中1的个数)) dp[i][k2][k3]=max(dp[i][k2][k3],dp[i−1][k1][k2]+(k3中1的个数))。
~~~~~ 空间会炸,用滚动数组。还有要记得特判一下 n = 1 n=1 n=1 的情况。
~~~~~ 时间复杂度 O ( N × 6 0 3 ) O(N\times 60^3) O(N×603),空间复杂度 O ( ( 2 10 ) 2 ) O((2^{10})^2) O((210)2)。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
vector<ll>vec;string s[105];
ll n,m,g[105],dp[2][1030][1030];
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(ll i=1;i<=n;i++){
cin>>s[i];s[i]='.'+s[i];
for(ll j=1;j<=m;j++)
g[i]=g[i]*2+(s[i][j]=='P'?1:0);
}
for(ll i=0;i<(1ll<<m);i++){
ll op=1;
for(ll j=0;j<m;j++){
if((i&(1ll<<j))&&((i&(1ll<<(j+1)))||(i&(1ll<<(j+2)))))
{op=0;break;}
}
if(op)vec.push_back(i);
}
if(n==1){
ll ans=0;
for(ll k1:vec){
if((k1&g[1])!=k1)continue;
ll asd=0;
for(ll j=0;j<m;j++)
asd+=(bool)(k1&(1ll<<j));
ans=max(ans,asd);
}
cout<<ans;
return 0;
}
for(ll k1:vec){
if((k1&g[1])!=k1)continue;
for(ll k2:vec){
if((k2&g[2])!=k2)continue;
if(k1&k2)continue;
ll asd=0;
for(ll j=0;j<m;j++)
asd+=(bool)(k1&(1ll<<j))+(bool)(k2&(1ll<<j));
dp[0][k1][k2]=asd;
}
}
for(ll i=3;i<=n;i++){
for(ll k1:vec){
if((k1&g[i-2])!=k1)continue;
for(ll k2:vec){
if((k2&g[i-1])!=k2)continue;
if(k1&k2)continue;
for(ll k3:vec){
if((k3&g[i])!=k3)continue;
if((k1&k3)||(k2&k3))continue;
ll asd=0;
for(ll j=0;j<m;j++)
asd+=(bool)(k3&(1ll<<j));
dp[1][k2][k3]=max(dp[1][k2][k3],dp[0][k1][k2]+asd);
}
}
}
for(ll k1:vec)
for(ll k2:vec)
dp[0][k1][k2]=dp[1][k1][k2];
}
ll ans=0;
for(ll k1:vec)
for(ll k2:vec)
ans=max(ans,dp[1][k1][k2]);
cout<<ans;
return 0;
}