状压DP

一.前言

二进制位运算符:&(与),|(或),^(异或)
0&0=0; 0&1=0; 1&1=1;(与运算): &
0$|0=0;0|1=1;1|$1=1; (或运算):  |
0^0=0; 1^=1; 1^1=0;(异或运算): ^
与操作:同为1才为1
或操作:有1就为1
或与操作:有1有0才为1

常用操作:
1.二进制意义下的去掉最后一位(相当于x/2):x>>1
2.二进制意义下的最后一位加零(相当于x*2):x<<1
3.二进制意义下的把最后一位变为1(xxx—>xx1):x|1
4.二进制意义下的把最后一位变为0(xxx—>xx0):x|1-1
5.二进制意义下的最后一位取反:x^1

稍微复杂一点的操作:
6.判断一个数字x二进制下第i位是不是等于1:(也可以理解成判断第i个点在不在集合中)
if((1<<(i-1))&x>0) 为真即说明等于1

7.将一个数字x二进制下第i位更改成1:
x=x|(1<<(i-1))

8.把一个数字二进制下最靠右的第一个1去掉:
x=x&(x-1)

9.枚举s的子集:
for(int i=s;i;i=(i-1)&s){}

10.若s是u的子集,那么s对于u的补集v:
v=s^u

二.状压DP

1.适用情景
题目设计图/物品的状态只有两种(是/不是),用二进制表示状态(比如地图中一整行的0010000…)
即二进制存状态,判断状态的合法性,最后输出合法状态的方案数目。

2.状压DP常用格式
一般来讲,状压DP处理的数据范围很小,但是一般比爆搜的范围稍微大一些,就是在两位数的范围内。

int maxn=1<<n; //规定状态的上界
for (int i=0;i<maxn;i++){
    if (i&(i<<1)) continue;//如果i情况不成立就忽略
    Type[++top]=i;//记录情况i到Type数组中
}
for (int i=1;i<=top;i++){
    if (fit(situation[1],Type[i]))
        dp[1][Type[i]]=1;//初始化第一层
}
for (int i=2;i<=层数(dp上界);i++){
    for (int l=1;l<=top;l++)//穷举本层情况
        for (int j=1;j<=top;j++)//穷举上一层情况(上一层对本层有影响时)
            if (situation[i],Type[l]和Type[j]符合题意)
                dp[i][l]=dp[i][l]+dp[i-1][j];//改变当前层(i)的状态(l)的方案种数
}
for (int i=1;i<=top;i++) ans+=dp[上界][Type[i]];

3.题目:
注意:
1.状压DP的题目涉及二进制运算符,所以一定要注意二进制运算时加括号,否则很容易错。
2.另外定义数组大小的时候最好不要用二进制表示,如:mp[2][1>>12](这样很容易发生错误),就用一个数值表示.

1.HRBUST - 1823 铺地砖
状压方程为dp[j][nxt]=sum(dp[j-1][state])

//状压DP例题 HRBUST - 2186 铺地砖
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
int N, M;
long long dp[42][8];
//第i列,搜索的当前行数, 第i列的状态,第j+1列的状态 
void dfs(int i,int j,int state,int nex)
{
	if(j==N)//遍历第i列结束 
	{
		dp[i+1][nex]+=dp[i][state]; 
		return;
	}
	//如果这个位置已经被上一列所占用,直接跳过
	if (((1<<j)&state)>0)
		dfs(i,j+1,state,nex);
		
	//如果这个位置是空的,尝试放一个1*2的
	if (((1<<j)&state)==0)
		dfs(i,j+1,state,nex|(1<<j));//改变了下一列的状态
	 
	//如果这个位置以及下一个位置都是空的,尝试放一个2*1的
	if (j+1<N&& ((1<<j)&state)==0 && ((1<<(j+1))&state)==0)
		dfs(i,j+2,state,nex);
	return;
}
 
int main()
{
	N=3;
	while (cin>>M)
	{
		memset(dp,0,sizeof(dp));
		dp[1][0]=1;
		for (int i=1;i<=M;i++){ //dp第i列 
			for (int j=0;j<(1<<N);j++) //遍历第i列的状态 
			if (dp[i][j]){
				dfs(i,0,j,0);
			}
		}
		cout<<dp[M+1][0]<<endl;
	}
}

2.P1879 [USACO06NOV]玉米田Corn Fields

#include<iostream>
using namespace std;
#define mod 100000000 
int mp[15][15],dp[15][4096];
int all_state[4096],now_state[15];
int main(){
	int N,M;
	scanf("%d%d",&N,&M);
	//mp[i][j]地图 
	for(int i=1;i<=N;i++)
	for(int j=1;j<=M;j++)
	cin>>mp[i][j];
	//now_state[i]存第i行的初始状态
    for(int i=1;i<=N;i++)
	for(int j=1;j<=M;j++){
		now_state[i]=(now_state[i]<<1)+mp[i][j];//now_state[i]=1说明第i行的状态不存在有两个临近的1(即合法)
	}
	int all=1<<M;
	//预处理所有可能状态i的合法性
	for(int i=0;i<all;i++){
		if((i&(i<<1))==0&&((i&(i>>1))==0))
			all_state[i]=1;
    }
	dp[0][0]=1; 
	//开始DP
	for(int i=1;i<=N;i++){//第i行
		for(int j=0;j<all;j++){//遍历第i行所有可能的状态j
			if((all_state[j]==1)&&((j&now_state[i])==j)){ //状态j合法且 now_state[i]在某位值是0的情况下j的值也必须是0 
				for(int k=0;k<all;k++){//第i-1行的状态
					if((k&j)==0){//状态合法---说明不存在同一位上都为1 
						dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
					}
				}
			}
		}
	} 
	int ans=0;
	for(int i=0;i<all;i++){ //所有合法状态个数 
		ans=(ans+dp[N][i])%mod;
	}
	cout<<ans<<endl;
}

3.OpenJ_Bailian - 4124 海贼王之伟大航路

当题目给出可种范围时,学状压dp之前的我的第一想法肯定是暴力将范围与我选择的情况一一对比,然而我们有‘&’这种神奇的运算符——我们发现,在‘&’之后,如果结果与原先选择的情况相等,代表可种。(只要一个不可种,即check位是0,结果也是0,则不同了)

#include <bits/stdc++.h>
using namespace std;
#define maxn 65540
int n;
int dp[20][maxn];
int mp[20][20];
#define inf 0x3f3f3f3f
int main()
{
	while(~scanf("%d",&n)){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				cin>>mp[i][j];
			}
		}
		memset(dp,inf,sizeof(dp));	
		dp[1][1]=0;//从岛1出发
		int i,j,k,fi,fj;
		for(int k = 1;k<(1<<n);k++){//k表示状态,k中第v位为0表示没去过,为1表示去过;
            for(i = 1,fi = 1;i <= n;i++,fi <<= 1){//到达第i个岛
                if(!(k & fi)) continue;//没去过第i个岛
                for(j = 1,fj = 1;j <= n;j++,fj <<= 1){//途径第j个岛
                    if(i == j || !(k & fj)) continue;//岛i与j相等,或者没去过j岛
                    dp[i][k] = min(dp[i][k],dp[j][k^fi]+mp[j][i]);
                }
            }
        }
        int f=(1<<n)-1;
        cout<<dp[n][f]<<endl;
	}
    return 0;
}

4.最小总代价(Vijos-1456)

5.胜利大逃亡(续)(Hdoj-1429)

参考博客:https://blog.csdn.net/u011077606/article/details/43487421
https://blog.csdn.net/LBCLZMB/article/details/96574249

题目列表:https://blog.csdn.net/u011077606/article/details/43487421

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值