状压dp2
前言
二维状态压缩动态dp的核心在于处理行间状态依赖,通常用于网格类问题(如棋盘覆盖、放置问题等)。
| 核心思想 | 重点 | |
|---|---|---|
| 1.状态设计 | 将当前行和前一行的状态共同编码,以处理行间约束 | 如何选择状态维度以覆盖所有约束条件 |
| 2.状态转移方程 | 通过位运算判断状态间的合法性,推导转移条件。 | 行间约束,确保合法;合法性检查,通过位运算快速验证条件;处理,需判断连续位是否合法,可能涉及多行。 |
| 3. 状态时空间优化 | 滚动数组和预处理合法状态 |
P1879 [USACO06NOV] Corn Fields G
状态定义
f[i][j]为第i行状态为j的方案数
不考虑泥土贫瘠的情况下冲突分两种
1.横向冲突 用当前行状态i和(i>>1)判断,在(i>>1)后每一位都和原来的左边相对应,这是可一用 ' & '如果结果为0,那i就是一个合法状态,应为这说明所有的位都是1对0 或 0对0,这样就没有相邻两位冲突的情况
2.纵向冲突 当前行状态i和前一行状态j,如果(i&j)==0这说明没有出现在同一位出现都为1的情况,这就是一个合法的情况
重新看土地贫瘠的情况
用soil[i]记录每行的状态,如果soil[i]&i是i,这说明没有出现某一位i为1而soil[i]为0的情况这就是一个合法的状态
另外对于横向冲突可一预处理在数组里
初始化
f[0][0]=1,指第0行,状态为0即没有放草的状态,这种情况的方案数为1
转移
首先肯定要枚举一个i为行数,枚举一个j和k为当前行状态和前一行状态,先枚举行数的原因是当处理到i行时,不管如何枚举,前一行的状态已经计算完毕,而如果先枚举状态j时,k有可能小于它,这是该状态还没有计算,对答案造成了影响
所以最终转移方法为先按照前文给出的判断状态合法性,f[i][tmp[j]]=(f[i][tmp[j]]+f[i-1][tmp[k]])%mod
最重答案
最终答案就是第n行左右合法状态的方案数的和
code
#include<bits/stdc++.h>
#define ll long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N=(1<<12)+10;
int n,m,soil[N],f[20][N];
int M,tmp[N],cnt,ans;
const int mod=1e8;
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
M=(1<<m)-1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int in;
cin>>in;
soil[i]=(soil[i]<<1)+in;
}
}
for(int i=0;i<=M;i++){
if((i&(i>>1))==0)tmp[++cnt]=i;
}
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=cnt;j++){
if((tmp[j]&soil[i])!=tmp[j])continue;
for(int k=1;k<=cnt;k++){
if((tmp[j]&tmp[k])==0)f[i][tmp[j]]=(f[i][tmp[j]]+f[i-1][tmp[k]])%mod;
}
}
}
for(int i=1;i<=cnt;i++){
ans=(ans+f[n][tmp[i]])%mod;
}
cout<<ans<<'\n';
return 0;
}
P8756 [蓝桥杯 2021 省 AB2] 国际象棋
发现m大n小,所以可以反过来方便枚举
状态
f[i][l][j][h]表示第i行,当前状态是j,前一行状态是l,已经放了h个棋子的方案数,后面解释
合法判断
没有横向判断,但需要判断前一行和前前行的冲突情况,不考虑后面,因为后面会判断自己,用k表示前前行,l表示前一行,用(k<<1),(k>>1),(l<<2),(l>>2)与j判断,是要有一个不合法就跳出,左右移一位可以与相邻位置判断,左右移两位符合题意中马的走法
此外因为答案与个数有关所以还要统计一个数组代表状态i的马的个数
转移
枚举顺序的原因与前面基本相同
枚举时先枚举行i,枚举当前状态j,前前行状态k,前一行状态k,当前已经放了的个数h,f[i][l][j][h]=(f[i][l][j][h]+f[i-1][k][l][h-val[j]])%mod
初始
初始f[0][0][0]][0]=1,并计算每一个状态i的包含棋子数val[i]
最终答案
最终答案为最后一行,个数为N,的所有合法状态之和
#include<bits/stdc++.h>
#define ll long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N=(1<<6);
const int mod=1e9+7;
int n,m,Num,Maxi,ans;
int val[N],f[110][N][N][21];
int count(int x){
int sum=0;
while(x){
sum+=(x&1);
x>>=1;
}
return sum;
}
int main(){
cin>>n>>m>>Num;
f[0][0][0][0]=1;
Maxi=(1<<n)-1;
for(int i=0;i<=Maxi;i++){
val[i]=count(i);
}
for(int i=1;i<=m;i++){
for(int j=0;j<=Maxi;j++){
for(int k=0;k<=Maxi;k++){
for(int l=0;l<=Maxi;l++){
for(int h=val[j];h<=Num;h++){
if(((j&(k<<1))||(j&(k>>1))||(j&(l<<2))||(j&(l>>2)))==0){
f[i][l][j][h]=(f[i][l][j][h]+f[i-1][k][l][h-val[j]])%mod;
}
}
}
}
}
}
for(int i=0;i<=Maxi;i++){
for(int j=0;j<=Maxi;j++){
ans=(ans+f[m][i][j][Num])%mod;
}
}
cout<<ans<<'\n';
return 0;
}
P2704 [NOI2001] 炮兵阵地
状态
f[i][j][k]表示第i行,状态为j,前一行状态是k的最大数量
转移
冲突:
左右冲入和地形问题和第一题基本一样,纵向冲入也和第二题差不多,在枚举时枚举一个前前行,纵向判断和数量统计也是和前面一样的,其他的都没什么区别
答案&初始化
和前面是一样的
#include<bits/stdc++.h>
#define ll long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N = 110;
int n,m,f[N][N][N];
int op[N*10],cur[N],ans;
char A[N][N];
int num[N*10],cnt;
bool ch(int x){
if(x&(x<<1))return 0;
if(x&(x<<2))return 0;
return 1;
}
bool ch2(int x,int y){
if(x&y)return 1;
return 0;
}
bool fit(int x,int k){
if(x&cur[k])return 0;
return 1;
}
int count(int x){
int sum=0;
while(x){
sum+=(x&1);
x>>=1;
}
return sum;
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++){
cur[i]=0;
for(int j=1;j<=m;j++){
cin>>A[i][j];
if(A[i][j]=='H') cur[i]+=(1<<(j-1));
}
}
memset(f,-1,sizeof(f));
for(int i=0;i<(1<<m);i++){
if(ch(i)) {
op[cnt++]=i;
}
}
f[0][0][0]=0;
for(int i=1;i<cnt;i++){
num[i]=count(op[i]);
}
for(int i=1;i<=n;i++){
for(int j=0;j<cnt;j++)
{
if(!fit(op[j],i))continue;
for(int k=0;k<cnt;k++){
if(ch2(op[j],op[k]))continue;
for(int t=0;t<cnt;t++){
if(ch2(op[j],op[t]))continue;
if(f[i-1][k][t]==-1)continue;
f[i][j][k]=max(f[i][j][k],f[i-1][k][t]+num[j]);
if(i==n)ans=max(ans,f[i][j][k]);
}
}
}
}
cout<<ans<<'\n';
return 0;
}

浙公网安备 33010602011771号