[POJ 1185] 炮兵阵地

Link:

POJ 1185 传送门

Solution:

看大家都说是一道状压$dp$基础题,结果我还是卡题了

发现决策第$i$行时,要同时考虑$i-1$和$i-2$行,因此状态中要包含两个“行”状态位

但$O(1024^3*100)$的复杂度明显不行啊,于是我这个sillybe就在这卡住了……

其实可以发现仅考虑“行合法”的行状态与前后行无关,可以预处理,而且只有不到70种!

于是将原来的$1024$种行状态缩减为已保证“行合法”的$70$中状态即可

这里对每行集体判断和 [BZOJ 2734] 集合取数  中 求表格上任意两点不相邻的点集数 的判断方式类似

用 !(st[l]&st[j]) 和 !(st[l]&st[k]) 判断与上两行的列是否有冲突

同时将原来输入的'N'、'P',转换为'1'、'0',判断位与是否不为0

Code:

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

const int MAXN=110,MST=70;
int n,m,dp[MAXN][MST][MST],dat[MAXN],sum[MST],st[MST],tot=0,res=0;

int cal(int x) //计算其中1的个数
{
    int ret=0;
    while(x) ret+=(x&1),x>>=1;
    return ret;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) for(int j=0;j<m;j++)
    {
        char ch=getchar();
        while(ch!='P' && ch!='H') ch=getchar();
        if(ch=='H') dat[i]|=(1<<j);
    }
    for(int i=0;i<(1<<m);i++) //预处理出一行可能的状态 
        if(!(i&(i<<1)) && !(i&(i<<2))) st[++tot]=i,sum[tot]=cal(i);
    
    for(int i=1;i<=tot;i++)
        if(!(st[i]&dat[1])) dp[1][1][i]=sum[i];
    for(int i=1;i<n;i++)
        for(int j=1;j<=tot;j++)
            for(int k=1;k<=tot;k++)
                if(!(st[j]&st[k]) && dp[i][j][k])
                    for(int l=1;l<=tot;l++) //通过位移和位与集体判断
                        if(!(st[l]&st[j]) && !(st[l]&st[k]) && !(st[l]&dat[i+1]))
                            dp[i+1][k][l]=max(dp[i+1][k][l],dp[i][j][k]+sum[l]);
    
    for(int i=1;i<=tot;i++) 
        for(int j=1;j<=tot;j++)
            res=max(res,dp[n][i][j]);
    printf("%d",res);
    return 0;
}

Review:

(1)查看有没有可以事先预处理出的合法的状态来对总数缩减

(2)利用 移位 + 位与 的方式对“行状态”整体判断算是常用套路了吧

猜你喜欢

转载自www.cnblogs.com/newera/p/9157614.html