【NOI2001】【Luogu 2704】【POJ1185】炮兵阵地

状压DP。因为m<=10,所以把每一行部署炮兵部队的状态作为“状态”,二进制的每一位1表示部署,0表示没有。同时把山地表示为1,平原表示为0,第i行的地形为dx[i]。

因为从上到下考虑,考虑第i行时要考虑第i-1,i-2行,而考虑i-1行时又要考虑第i-2,i-3行……所以令f[i][j][k]表示前i行,第i行状态为j,第i-1行状态为k时可以部署炮兵部队的最大值。

可得以下方程:

$f[i][j][k]=\max(f[i-1][k][l]+cnt[j])(j,k,l\in S )$

同时j&k==0,j&l==0,j&dx[i]==0。

初始化:$f[0][0][0]=0$,其它为$-\infty$。

S为一个行状态的合法集合,即状态中“1”两两之间间隔超过2。若一个状态i满足

!(i&(i<<1))&&!(i&(i<<2))

那么i是合法状态。

答案:$\max(f[n][i][j]) (i,j\in S)$
因为已初始化,所以找答案时可不用判断i&j是否为0。
事实上,m=10时|S|=60,为了节省空间,可以把j,k定义为集合中相应状态的编号(下标)。框架中只要改初始化:
$f[0][1][1]=0$(为什么,见代码↓)

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,s[102],cnt[102],tot;//s是可用集合,cnt是1的数目 
int f[102][72][72];//f的2、3维都是可用状态的编号
//经试验m=10时|s|=60,所以可以放心少开 
int dixing[102];//0平原,1山地,状态&地形!=0即炮兵在山地上 
char str[13];
void input()//输入O(nm) 
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        scanf("%s",str);
        for(int j=0;j<m;++j)
        {
            dixing[i]<<=1;
            if (str[j]=='H')
                dixing[i]|=1;
        }
    }
}
int count1(int x)//计算1的个数 
{
    int sum=0;
    while(x) x-=x&-x,++sum;
    return sum;
}
void prework()//预处理合法集合O(2^m) 
{
    for(int i=0;i<1<<m;++i)
        if (!(i&(i<<1))&&!(i&(i<<2)))
        {
            s[++tot]=i;
            cnt[tot]=count1(i);//计算1的个数 
        }
}
int main()
{
    input();
    prework();
    memset(f,0xcf,sizeof(f));//f数组初始化 
    f[0][1][1]=0;
    for(int i=1;i<=n;++i)//DP O(n|s|^3) 
    for(int j=1;j<=tot;++j)
    for(int k=1;k<=tot;++k)
    for(int l=1;l<=tot;++l)
    if ((s[j]&s[k])==0&&(s[j]&s[l])==0&&(s[j]&dixing[i])==0)
        f[i][j][k]=max(f[i-1][k][l]+cnt[j],f[i][j][k]);
    int ans=0;//寻找答案 
    for(int i=1;i<=tot;++i)
        for(int j=1;j<=tot;++j)
            ans=max(f[n][i][j],ans);
    printf("%d",ans);
    return 0;
}
View Code

 

猜你喜欢

转载自www.cnblogs.com/xzs123456/p/10988083.html