状压dp临行枚举类问题

像给你一个棋盘,摆放一些棋子,相邻行之间摆放的棋子会互相影响这类问题,可以用状压 DP 解决。

流程

枚举当前行和上一行的状态,判断是否合法,然后进行转移。

例题

洛谷 P1879 [USACO06NOV] Corn Fields G

设 \(dp[i,state]\) 为前 \(i\) 行,第 \(i\) 行摆放的状态为 \(state\) 时的方案数。

for i from 0 to n-1:
	for j from 0 to (2^n)-1://当前行种草状态
		for k from 0 to (2^n)-1://上一行种草状态
			if !(j&k) && j&a[i]==j && k&a[i]==k://相邻两行不冲突,并且贫瘠的土地没有种草
				dp[i,j]+=dp[i-1,k]

时间复杂度:\(O(n \times 2^{2n})\) boom!

考虑预处理出合法状态,这样枚举的状态数可以从 4000 左右降到 400 左右

极端时间复杂度:\(O(12\times 400^2)\approx 10^6\) 可过。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
bool v[15][15];
int f[15][405],dp[15][1<<12+1],sum[15];
int n,m;
const int mod=100000000;
void init(){
	for(int i=0;i<n;i++){
		for(int j=0;j<(1<<m);j++){
			bool firefragments=1;
			for(int k=0;k<m;k++){
				if(( ((j>>k)&1) && v[i][k]==0) || (((j>>k)&1) && ((j>>k)&1) == ((j>>k-1)&1))){
					firefragments=0;
					break;
				}
			}
			if(firefragments) f[i][++sum[i]]=j;
		}
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
			cin>>v[i][j];
	init();
	int ans=0;
	for(int i=1;i<=sum[0];i++) dp[0][i]=1;
	for(int i=0;i<n;i++){
		for(int j=1;j<=sum[i];j++){
			for(int k=1;k<=sum[i-1];k++){
				if(!(f[i][j]&f[i-1][k])){
					dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
				}
			}
		}
	}
	for(int i=1;i<=sum[n-1];i++) ans=(ans+dp[n-1][i])%mod;
	cout<<ans;
	return 0;
} 

洛谷 P1896 [SCOI2005] 互不侵犯

跟上一题差不多,只不过多了个限制摆放个数,还有斜着也会冲突。

设 \(dp[i,state]\) 为前 \(i\) 行,第 \(i\) 行摆放的状态为 \(state\),当前一共放了 \(j\) 个国王时的方案数。

for i from 1 to n-1:
	for j from 0 to cnt://当前行放置状态
		for k from 0 to cnt://上一行放置状态
			if !(j&k) && !((j<<1)&k) && !((j>>1)&k):
				for l from k to sum[j]:
					dp[i,j,l]+=dp[i,k,l-sum[j]]

\(cnt\) 表示合法状态个数,\(sum[i]\) 表示第 \(i\) 个合法状态摆放国王的个数。

可以使用 dfs 预处理出合法状态,代码实现中,为了节省空间,第二维存的是合法状态编号。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int f[2005],dp[20][3000][200],s[2005];
int n,k_,cnt;
void init(int tai,int num,int j){
	if(j>=n){
		f[++cnt]=tai;
		s[cnt]=num;
		return;
	}
	init(tai,num,j+1);
	init(tai+(1<<j),num+1,j+2);
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>k_;
	init(0,0,0);
	for(int i=1;i<=cnt;i++) dp[1][i][s[i]]=1;
	for(int i=2;i<=n;i++){
		for(int j=1;j<=cnt;j++){
			for(int k=1;k<=cnt;k++){
				if( (f[j]&f[k]) || ((f[j]<<1)&f[k]) || ((f[j]>>1)&f[k])) continue;
				for(int l=k_;l>=s[j];l--){
					dp[i][j][l]+=dp[i-1][k][l-s[j]];
				}
			}
		}
	}
	int ans=0;
	for(int i=1;i<=cnt;i++) ans+=dp[n][i][k_];
	cout<<ans;
	return 0;
} 

洛谷 P2704 [NOI2001] 炮兵阵地

典中典毒瘤题。

因为一个炮兵会受上一行和上上行和炮兵影响,所以要枚举上一行和上上行。

设 \(dp[i][j][k]\) 为前 \(i\) 行,第 \(i\) 行状态为 \(j\),第 \(i-1\) 行状态为 \(k\) 时的最大摆放数。

状态转移:\(dp[i][j][k]=\max(dp[i-1][k][l])+sum[j]\)

其中 \(l\) 为上上行放置的状态。

代码实现中,为了节省空间,存的是合法状态编号而不是状态本身。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[110],dp[101][701][701];
vector<int> f,sum;
void init(int tai,int num,int j){
	if(j>=m){
		f.push_back(tai);
		sum.push_back(num);
		return;
	}
	init(tai,num,j+1);
	init(tai+(1<<j),num+1,j+3);
}
int ans=0;
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=0;j<m;j++){
			char c;
			cin>>c;
			if(c=='P') a[i]|=(1<<j);
		}
	}
	init(0,0,0);
	dp[0][0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<f.size();j++){
			if((f[j]&a[i])==f[j]){
				for(int k=0;k<f.size();k++){
					if((f[k]&a[i-1])==f[k]){
						for(int l=0;l<f.size();l++){
							if((f[l]&a[i-2])==f[l]&&!(f[j]&f[k])&&!(f[j]&f[l])&&!(f[l]&f[k])){
								dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+sum[j]);
							}
						}
						ans=max(ans,dp[i][j][k]);
					}
				}
			}
		}
	}
	cout<<ans;
	return 0;
}
posted @ 2026-02-23 19:13  vivid/stasis  阅读(30)  评论(0)    收藏  举报