状压dp总结+例题

f[i][st]表示前i行,第i行的放置状态为st的方案数。

例题:

1.Mondriaan’s Dream POJ - 2411
题意:给出两个数 n、m,现在有 12、21 两种类型的方块,要求将 n*m 的区域填满,问一共有多少种方案

思路:木块有两种放置方法,横放或竖放,我们按行dp,横为11,竖为01(竖)。
1.确定第一行的放置方式。
2.从第二行开始dp,两行一起check。
3.确定最后一行的放置方式一定是全1,输出f[h][tot]

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int h,w;
bool init(int s){
	for(int j=0;j<w;){
	
	if(s&(1<<j)){
		if(j==w-1)return false;
		if(s&(1<<(j+1)))j+=2;
		else return false;
	}
	else j++;
}
return true;
}
long long f[12][1<<12];
bool check(int now,int pre){
	for(int j=0;j<w;){
		if(now&(1<<j)){
			if(pre&(1<<j)){
				if(j==w-1||!(now&(1<<(j+1)))||!(pre&(1<<(j+1))))return false;
				else j+=2;
			}
			else j++;
		}
		else{
			if(pre&(1<<j))j++;
			else return false;
		}
	}
	return true;
}
int main(){
	while(~scanf("%d%d",&h,&w)){
		if(h==0&&w==0)break;
		if((h&1)&&(w&1)){
			printf("0\n");
			continue;
		}
		if(h<w)swap(h,w);
		int tot=(1<<w)-1;
		memset(f,0,sizeof(f));
		for(int s=0;s<=tot;s++){
			if(init(s)){
			f[1][s]=1;
		//	cout<<s<<"s"<<endl;
		}
	}
		for(int i=2;i<=h;i++){
			for(int j=0;j<=tot;j++){
				for(int k=0;k<=tot;k++){
					if(check(j,k)){
						f[i][j]+=f[i-1][k];
					}
				}
			}
		}
		printf("%lld\n",f[h][tot]);
	}
	
	
	return 0;
}

2.BZOJ 1087: [SCOI2005]互不侵犯King
链接
本题不需要初始化第一行的状态,直接判断所有状态的1的个数,在dp判断中看这个状态是否合理,合理进行dp,本行状态由上一行推来。这里要多开一维保存放置了多少king,所以转移时要多加一层循环。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
ll ans,f[10][512][26];
int g[512];
int n,m;
int main(){
	scanf("%d%d",&n,&m);
	if(m>25||m>=n*n){
		printf("0\n");
	}
	else{
		int t=1<<n;
		f[0][0][0]=1;
		g[0]=0;
		for(int i=1;i<t;i++){
			g[i]=g[i>>1]+(i&1);
		}
		for(int i=1;i<=n;i++){
			for(int j=0;j<t;j++){
				if(g[j]<=m&&!(j&(j>>1))){
					for(int k=0;k<t;k++){
						if(g[k]<=m&&!(k&(k>>1))&&!(k&j)&&!(j&(k>>1))&&!(j&(k<<1))){
							for(int l=g[j]+g[k];l<=m;l++){
								f[i][j][l]+=f[i-1][k][l-g[j]];
							}
						}
					}
				}
			}
		}
		ans=0;
		for(int i=0;i<t;i++){
			ans+=f[n][i][m];
		}
		printf("%d\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值