题解:P2704 [NOI2001] 炮兵阵地 状压dp

第一次状压的话建议先做:P1896 [SCOI2005] 互不侵犯

题目链接:P2704 [NOI2001] 炮兵阵地

题目要点如下:

  1. 每个炮兵会打到上下两行,左右各两格的队友

  2. 有地形限制,山上不能放炮兵

考虑状态定义:

第一维:由于会影响上下两行,所以要以两为单位来规划。

第二维:要枚举与当前双行状态相容的上面的(也就是第i-2和i-1行的)双行状态,所以要把当前行的状态记录下来~~(这不废话~~

所以状态表示:

f[i][j]表示(i与i+1行)状态为j时,能发下的最多炮兵个数

那么方程也就很简单了,只要找到与最大的与当前状态相容的f[i-2][k],加上当前状态的炮兵个数就行了:

f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 2 ] [ k ] ) + n u m [ j ] f[i][j]=max(f[i][j],f[i-2][k])+num[j] f[i][j]=max(f[i][j],f[i2][k])+num[j]

预处理:

  1. 穷举出单行合法状态

  2. 用单行合法状态穷举出双行合法状态

  3. 处理出双行的合法状态的后继双行合法状态

  4. (处理出双行合法状态的后继单行合法状态)

dp过程:

  1. 将第一行和第二行的状态初始化(与地形不相容的continue)

  2. 从第三行开始dp,枚举行

  3. 枚举当前行的状态(与地形不相容的continue)

  4. 枚举与当前行相容的状态(作为i-2与i-1的状态)

  5. 转移

由状态定义可知,max(f[n-1][i])即为所求

你是不是忘了n%2==1的情况了…

所以如果是奇数行的话,dp到第n-2和n-1行就停,最后单独dp一行

一些细枝末节的地方可以看看代码注释

#include
#define pii pair<int,int>
#define ll long long
using namespace std;
int n,m;
char a[120][10];
int num1[70];int cnt1;
int num2[2200];int cnt2;
int lf[120];
vector<int> st1;
vector<pii> st2;
vector<int> vt[2200];
vector<int> vt1[70];
ll f[120][2200];
ll ans;
inline void in(int &x){
    x=0;int y=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') y=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    x*=y;
}
bool check(int v){
    if(v&(v<<1)||v&(v<<2)||v&(v>>1)||v&(v>>2)) return 0;
    return 1;
}//检查当前状态是否合法
int get1(int v){
    int st=0;
    while(v){
        if(1&v) st++;
        v>>=1;
    }
    return st;
}//如名
void init(){
    for(int i=0; i<(1<<m); i++)
        if(check(i)) num1[cnt1++]=get1(i),st1.push_back(i);//枚举出单行可行状态
    int s1=st1.size();
    for(int i=0; i<s1; i++){
        for(int j=0; j<s1; j++){
            if(!(st1[i]&st1[j])) st2.push_back(make_pair(st1[i],st1[j])),num2[cnt2++]+=num1[i]+num1[j];
        }//如果上下相容即可捆在一起
    }//枚举出双行可行状态
    int s2=st2.size();
//    for(int i=0; i
//        printf("%d ",num2[i]);
//    }
    for(int i=0; i<s2; i++){
        for(int j=0; j<s2; j++){
            if(!((st2[i].first&st2[j].first)||(st2[i].second&st2[j].second)||(st2[i].first&st2[j].second))){
                vt[i].push_back(j);//vt[i][j]表示双行状态i的第j个双行相容状态
            }
        }
    }//处理出双行的后继双行
    for(int i=0; i<s1; i++){
        for(int j=0; j<s2; j++){
            if(!(st1[i]&st2[j].first)&&!(st1[i]&st2[j].second)){
                vt1[i].push_back(j);//vt[i][j]表示双行状态i的第j个单行相容状态
            }
        }
    }//处理好每行的后继状态
}

signed main(){
    in(n);in(m);
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++)
            cin >> a[i][j];
    for(int i=1; i<=n; i++)
        for(int j=1; j<=m; j++){
            if(a[i][j]=='H'){
                lf[i]=lf[i]|(1<<(j-1));//构造地形状态
            }
        }
    init();//预处理
    int s1=st1.size();
    int s2=st2.size();
    for(int i=0; i<s2; i++)
        if(!(st2[i].first&lf[1])&&!(st2[i].second&lf[2]))
            f[1][i]=num2[i];
    for(int i=3; i<=(n&1?n-2:n-1); i+=2){//枚举第i与i+1行
        for(int j=0; j<s2; j++){//枚举当前双行状态
            if((st2[j].first&lf[i])||(st2[j].second&lf[i+1])) continue;//如果当前状态与地形不相容
            int vs=vt[j].size();
            for(int k=0; k<vs; k++){//枚举与当前双行相容的状态,即i-2与i-1的状态
                f[i][j]=max(f[i][j],f[i-2][vt[j][k]]);
            }
            f[i][j]+=num2[j];
        }
    }
    if(n&1){
        for(int i=0; i<s1; i++){
            if(st1[i]&lf[n]) continue;
            int siz=vt1[i].size();
            for(int j=0; j<siz; j++){
                f[n][i]=max(f[n][i],f[n-2][vt1[i][j]]+num1[i]);
            }
        }
        for(int i=0; i<s2; i++){
            ans=max(ans,f[n][i]);
        }
        return printf("%lld",ans)&0;
    }
    for(int j=0; j<s2; j++)
        ans=max(ans,f[n-1][j]);
    printf("%lld",ans);
    return 0;
}

你可能感兴趣的:(题解,算法,c++,动态规划)