[NOI2001][POJ1185]炮兵阵地(状压dp)

题目描述

传送门

题解

m很小并且山地和平原、放置与不放置可以用01表示,所以一看就是状压dp。
但是炮可以打到上下两个格子这一点很烦人,这样的话不能用前一行向当前行转移,因为有可能在前一行的前一行存在一个炮和当前行的冲突。
那么我们在状态表示里至少要存两行的状态。但是 2102 不是有点太大了么?有趣的是,每一行的炮与炮之间至少要留两个空格,所以每一行实际的方案数要比 210 要小很多,即使是最大的m=10也不过只有169个,这些合法的状态是可以预处理出来的。
设f(i,j,k)表示前i行,其中最后两行为第j个第k个状态的最大数量。然后就可以分别判断是否合法然后转移了。
理论时间复杂度是 O(n(2m)3) 。但是由于我们已经预处理了最多169个合法方案,中间各种判断又会砍掉很多冗余的状态,极限数据也是完全可以在2s内出解的。

代码

#include
#include
#include
using namespace std;
#define N 105

int n,m,tot,ans;
int a[N],sol[N*2],num[N*2],f[N][N*2][N*2];
char s[N];

int calc(int x)
{
    int ans=0;
    while (x)
    {
        if (x&1) ans++;
        x>>=1;
    }
    return ans;
}
int main()
{
    scanf("%d%d\n",&n,&m);
    for (int i=1;i<=n;++i)
    {
        gets(s);
        for (int j=1;j<=m;++j)
            if (s[j-1]=='P') a[i]+=(1<<(m-j));
    }
    for (int i=0;i<=(1<<m)-1;++i)
        if (!(i&(i<<2))&&!(i&(i<<1)))
        {
            sol[++sol[0]]=i;
            num[sol[0]]=calc(i);
        }
    if (n==1)
    {
        for (int i=1;i<=sol[0];++i)
            if ((sol[i]&a[1])==sol[i]) ans=max(ans,num[i]);
        printf("%d\n",ans);
        return 0;
    }
    for (int i=1;i<=sol[0];++i)
        if ((sol[i]&a[1])==sol[i])
            for (int j=1;j<=sol[0];++j)
                if ((sol[j]&a[2])==sol[j])
                    if (calc(sol[i]^sol[j])==num[i]+num[j])
                        f[2][i][j]=max(f[2][i][j],num[i]+num[j]);
    for (int i=3;i<=n;++i)
        for (int j=1;j<=sol[0];++j)
            if ((sol[j]&a[i])==sol[j])
                for (int k=1;k<=sol[0];++k)
                    if ((sol[k]&a[i-1])==sol[k])
                        for (int l=1;l<=sol[0];++l)
                            if ((sol[l]&a[i-2])==sol[l])
                                if (calc(sol[j]^sol[k])==num[j]+num[k]&&calc(sol[k]^sol[l])==num[k]+num[l]&&calc(sol[j]^sol[l])==num[j]+num[l])
                                    f[i][k][j]=max(f[i][k][j],f[i-1][l][k]+num[j]);
    for (int i=1;i<=sol[0];++i)
        if ((sol[i]&a[n])==sol[i])
            for (int j=1;j<=sol[0];++j)
                if ((sol[j]&a[n-1])==sol[j])
                    if (calc(sol[i]^sol[j])==num[i]+num[j])
                        ans=max(ans,f[n][j][i]);
    printf("%d\n",ans);
}

总结

①状压dp不要光考虑理论复杂度。合法状态有可能不是很多。
②状压dp某一个不要光考虑用一维表示某一个状态,其实也可以在预处理之后表示成第几个状态

你可能感兴趣的:(题解,dp,NOI)