洛谷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;
}

猜你喜欢

转载自www.cnblogs.com/Le-mon/p/9625529.html