洛谷2704 [NOI2001]炮兵阵地
题目戳这里
Solution
状压DP很好的入门题,用熟练位运算貌似也没那么难。
首先分析一下题目:
看见n=100,m=10,立马就想到了状压,看起来也像DP,所以我们还是采用行号为阶段的状压DP。
因为每个炮兵可以攻击到上面两行的范围,所以枚举i行状态时需要知道i-1和i-2行的状态。我们把每一行的状态看成一个M位的二进制数,第p位为1代表该行第p列放置了一个炮兵,0代表没有。
在DP前,我们先预处理出集合S,代表“相邻两个1的距离不小于3”的所有M位二进制数,g数组储存对应S集合中某个数含有的1的个数。然后还需要预处理出1行和2行状态。
那么状态定义也很明显了,f[j][k][i]表示第i行状态为j,第i-1行状态为k的方案数,那么只需枚举上一行状态和上两行状态便可以转移。
虽然M为的二进制数有\(2^M\)个,但是我们只枚举S集合里面的数(其他的不合法),所以时间复杂度为\(O(N|S|^2)\),事实上S集合非常小。
Coding
#include<bits/stdc++.h>
using namespace std;
const int N = 105;
int num,S[N*20],ans,n,m,f[N*20][N*20][101],sum[N*20],a[N],g[N];
int count(int x)
{
int num=0;
while(x)
{
if(x%2==1) num++;
x/=2;
}
return num;
}
int main()
{
char x;
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=0;j<m;j++)
{ cin>>x; if(x=='H') a[i]+=1<<j;}
for(int i=0;i<(1<<m);i++)//预处理S集合
if(((i&(i<<1))==0)&&((i&(i<<2))==0)&&((i&(i>>1))==0)&&((i&(i>>2))==0))
{
S[++num]=i;
g[num]=count(i);
if((i&a[1])==0) f[0][num][1]=g[num];
}
for(int i=1;i<=num;i++)//预处理前两行
for(int j=1;j<=num;j++)
if(((S[i]&S[j])==0)&&((S[j]&a[2])==0)) f[i][j][2]=max(f[i][j][2],f[0][i][1]+g[j]);
for(int i=3;i<=n;i++)
for(int j=1;j<=num;j++)
if((a[i]&S[j])==0)
for(int k=1;k<=num;k++)
if((S[k]&S[j])==0)
for(int last=1;last<=num;last++)
if(((S[last]&S[k])==0)&&((S[last]&S[j])==0)) f[k][j][i]=max(f[k][j][i],f[last][k][i-1]+g[j]);
for(int i=1;i<=num;i++)
for(int j=1;j<=num;j++)
ans=max(ans,f[i][j][n]);
cout<<ans;
return 0;
}