状压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;
}



posted @ 2025-08-01 14:51  wmq2012  阅读(12)  评论(0)    收藏  举报