算法总结—状压 DP2

二维状压 DP

二维状压 DP 一般是放置问题。

[USACO06NOV] Corn Fields G

状态

\(dp_{i,j}\) 放好前 \(i\) 行,且第 \(i\) 行状态为 \(j\) 的方案数。

答案

\(\sum^{2^n-1}_{i=0} dp_{m,i}\)

状态转移方程

先假装不分土地是否贫瘠。

  1. 横着看有没有相邻的 \(1\)

    设一行的状态为 \(i\),如果有两个相邻的 \(1\),则 i&(i<<1) 的结果中一定会包含 \(1\),所以只要 (i&(i<<1))==0,就说明状态为 \(i\) 是合法的。

  2. 竖着看有没有相邻的 \(1\)

    设这一行的状态为 \(i\),上一行的状态为 \(j\),如果竖着有两个相邻的 \(1\),则 i&j 的结果中一定会包含 \(1\),所以只要 (i&j)==0,就说明这两行是合法的。

区分土地是否贫瘠。

设一行中土地贫瘠的状态为 \(a\),现在选择的状态为 \(i\),则 \(i\) 一定是 \(a\) 的子集,即 (a&i)==i

\(dp_{i,j}=\sum^{2^m-1}_{k=0} dp_{i-1,k}\)

初始值

\(dp_{0,0}=1\)

代码

#include<bits/stdc++.h>
using namespace std;
const int mod=1e8;
int a[15];
int dp[15][4105];
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            int x;
            cin>>x;
            a[i]=a[i]*2+x;
        }
    } 
    dp[0][0]=1;
    int N=1<<m; 
    for(int i=1;i<=n;i++){
        for(int j=0;j<N;j++){
            if((j&(j<<1))==0&&(j&a[i])==j){
                for(int k=0;k<N;k++){
                    if((k&j)==0){
                        dp[i][j]=(dp[i][j]+dp[i-1][k]);
                    }
                }
            }
        }
    }
    int cnt=0;
    for(int i=0;i<N;i++){
        cnt=(cnt+dp[n][i])%mod;
    }
    cout<<cnt;
    return 0;
}

[蓝桥杯 2021 省 AB2] 国际象棋

状态

\(dp_{i,j,k,l}\) 放好前 \(i\) 行,且第 \(i\) 行状态为 \(j\), 第 \(i-1\) 行的状态为 \(k\),放了 \(l\) 个棋子的方案数。

答案

\(\sum^{2^n-1}_{i=0}\sum^{2^n-1}_{j=0} dp_{n,i,j,K}\)

状态转移方程

设这一行的状态为 \(i\),上一行的状态为 \(j\),上上行的状态为 \(k\)

  • 判断 \((x-1,y-2)\)(i&(k>>2))==0

  • 判断 \((x-2,y-1)\),(i&(l>>1))==0

  • 判断 \((x-2,y+1)\),(i&(l<<1))==0

  • 判断 \((x-1,y+2)\)(i&(k<<2))==0

\(dp_{i,j,k,p}=\sum^{2^m-1}_{l=0} dp_{i-1,k,l,p-s_j}\)\(s_x\) 表示 \(x\) 在二进制下有多少位 \(1\)

初始值

\(dp_{0,0,0,0}=1\)

代码

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int dp[105][70][70][25];
int one(int x){
    int cnt=0;
    while(x){
        cnt+=x%2;
        x/=2;
    }
    return cnt;
}
int s[70];
int main(){
    int n,m,K;
    cin>>m>>n>>K;
    dp[0][0][0][0]=1;
    int N=1<<m;
    for(int i=0;i<N;i++){
        s[i]=one(i);
    }   
    for(int i=1;i<=n;i++){
        for(int j=0;j<N;j++){
            for(int k=0;k<N;k++){
                for(int l=0;l<N;l++){
                    for(int p=s[j];p<=K;p++){
                        if((j&(l<<1))==0&&(j&(l>>1))==0&&(j&(k<<2))==0&&(j&(k>>2))==0){
                            dp[i][j][k][p]=(dp[i][j][k][p]+dp[i-1][k][l][p-s[j]])%mod;
                        }
                    }
                }
            }
        }
    }
    int cnt=0;
    for(int i=0;i<N;i++){
        for(int j=0;j<N;j++){
            cnt=(cnt+dp[n][i][j][K])%mod;
        }
    }
    cout<<cnt;
    return 0;
}

[NOI2001] 炮兵阵地

状态

\(dp_{i,j,k}\) 放好前 \(i\) 行,且第 \(i\) 行状态为 \(j\), 第 \(i-1\) 行的状态为 \(k\),最多可以放多少个炮兵。

答案

\(\max\{dp_{n,j,k}\}\)

状态转移方程

设一行中障碍的状态为 \(a\),放置状态为 \(i\),上一行放置状态为 \(j\) ,上上行放置状态为 \(k\)

(i&a)!=0&&(i&j)==0&&(i&l)==0 时状态合法。

\(dp_{i,j,k}=\max^{2^m-1}_{l=0} dp_{i-1,k,l+s_j}\)

初始值

\(dp_{i,j,k}=-\inf\)\(dp_{0,1,1}=0\)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[105];
int s[1050];
int dp[105][105][105];
int op[105];
bool f1(int x){
    if((x&(x<<1))||(x&(x<<2))){
        return 0;
    }
    return 1;
}
bool f2(int x,int y){
    if((x&a[y])){
        return 0;
    }
    return 1;
}
int one(int x){
    int cnt=0;
    while(x){
        cnt+=x%2;
        x/=2;
    }
    return cnt;
}
signed main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            char c;
            cin>>c;
            if(c=='H'){
                a[i]+=(1<<(j-1));
            }
        }
    }
    memset(dp,-0x3f,sizeof dp);
    int N=1<<m;
    int cnt=0;
    for(int i=0;i<N;i++){
        if(f1(i)){
            op[++cnt]=i;
        }
    }
    dp[0][1][1]=0;
    for(int i=1;i<=cnt;i++){
        s[i]=one(op[i]);
    }
    int Max=-1e9;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=cnt;j++){
            if(f2(op[j],i)){
                for(int k=1;k<=cnt;k++){
                    if((op[j]&op[k])==0){
                        for(int l=1;l<=cnt;l++){
                            if((op[j]&op[l])==0){
                                dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+s[j]);
                            }
                        }
                    }
                    if(i==n){
                        Max=max(Max,dp[i][j][k]);
                    }
                }
            }
        }
    }
    cout<<Max;
    return 0;
}
posted @ 2025-05-18 19:20  LRRabcd  阅读(23)  评论(0)    收藏  举报