算法总结—状压 DP2
二维状压 DP
二维状压 DP 一般是放置问题。
[USACO06NOV] Corn Fields G
状态
\(dp_{i,j}\) 放好前 \(i\) 行,且第 \(i\) 行状态为 \(j\) 的方案数。
答案
\(\sum^{2^n-1}_{i=0} dp_{m,i}\)
状态转移方程
先假装不分土地是否贫瘠。
-
横着看有没有相邻的 \(1\)。
设一行的状态为 \(i\),如果有两个相邻的 \(1\),则
i&(i<<1)的结果中一定会包含 \(1\),所以只要(i&(i<<1))==0,就说明状态为 \(i\) 是合法的。 -
竖着看有没有相邻的 \(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;
}

浙公网安备 33010602011771号