您的位置:首页 > 其它

状态压缩dp学习小记part1

2013-05-31 21:53 417 查看
论文:天津大学的周伟的《状态压缩》

上述论文在这里有部分参考代码:状态压缩递推(States Compressing Recursion,SCR)
题目及解题思路可以在这里找到: (原创)BUCToj 动态规划一状态压缩 Problem E-I 解题报告之五连发

从以下两篇博文中挑选了练习题目,并参考了题目的翻译……
状态压缩DP总结【POJ3254】【POJ1185】【POJ3311】【HDU3001】【POJ2288】【ZOJ4257】【POJ2411】【HDU3681】 - 我叫MK - 博客频道 - CSDN.NET
http://blog.csdn.net/accry/article/details/6607703
状态压缩DP 题目小节 (一) - ddyyxx的程序员之路 - 博客频道 - CSDN.NET
http://blog.csdn.net/dyx404514/article/details/8754537
常用位运算:

a |= 1<<bit  //置位
a &= ~(1<<bit)  //清位
(a & 1 << bit) != 0  //测位方法1
(a >> bit & 1) != 0    //测位方法2


常用函数

//测一个数的二进制表示中是否有间隔小于3的1
bool Ok (int x)
{
	if (x&(x<<1))
		return false;
	if (x&(x<<2))
		return false;
	return true;
}

//计算一个整型数x的二进制中1的个数
int Cal (int x)
{	
	int cnt=0;
	while (x)
	{
		cnt++;
		x&=(x-1);
	}
	return cnt;
}

//取所有子集
void Deal ()
{
	x=a;
	while (x)
		x = (x-1) & a;
}


以下我看懂的第一份状态压缩的代码……
题目链接:http://coder.buct.edu.cn/JudgeOnline/problem.php?cid=1032&pid=4
代码修改自:http://blog.csdn.net/zhang360896270/article/details/6596916

#include <cstdio>
#include <cstring>

long long a[1100000];

int main ()
{
	long long n;
	while (~scanf("%d",&n))
	{
		memset(a,0,sizeof(a));
		a[0]=1;
		for (int i=1; i<=1<<n ;i++)
		{//注意这里是1左移n位不是n<<1,显然这里是在枚举0000~1111的每一种状态
			for (int j=i;j>0; j -= (j&-j))
			{//注意这里是倒推,因为要由之前的状态推出现在的状态
				a[i] += a[i&~(j&-j)];
				//这里的位运算处理甚是漂亮,它保证每一次都刚好取到i的子集
				//首先j&-j可以得出在i之前的每一种状态j的最低位1的位置k
				//然后取反可以保证只有第k个位置刚好为0,那么求与之后就在原来i的基础上去除了第k个1
				//比如说当前i枚举到0111,那么j&-j = 0001,则~(j&-j) = 1110,那么i&1110 = 0110,0110就是0111的一个子集
				//随后去掉当前最低位k,j变成0110,以此反复运算,直到j=0000
			}    
		}
		printf("%lld\n",a[(1<<n)-1]);
	}  
	return 0;    
}


SGU 222

和上面那道差不多,用组合做更好些

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

const int N=1<<10;
int pre

;
int sum
;  //sum[i]表示十进制数i转成二进制后1的个数
int dp[11]
;
int n,k;

int main ()
{
	int i,j;
	scanf("%d%d",&n,&k);
	if (k>n)
	{
		printf("0\n");
		return 0;
	}
	sum[0]=0;
	int all=1<<n;
	for (i=1;i<all;i++)    //预处理
		sum[i]=sum[i>>1]+i%2;
	for (i=0;i<all;i++)
		for (j=0;j<all;j++)
			if (sum[j]>sum[i] && sum[j^i]==1)  //sum[j]>sum[i]保证状态j一定在i之后,sum[j^i]==1保证i为j的子状态
				pre[j][++pre[j][0]]=i;  //pre[j][0]记录状态j共有多少个前状态,pre[j][]记录前状态都有哪些
	dp[0][0]=1;
	int t,ans=0;
	for (i=1;i<=n;i++)
		for (j=0;j<all;j++)
		{
			if (sum[j]>i)
				continue;
			for (t=1;t<=pre[j][0];t++)
				dp[i][j] += dp[i-1][pre[j][t]];
			if (sum[j]<i)  //等价于sum[j]+1<=i,表示如果在这行不放棋子 
				dp[i][j] += dp[i-1][j];
			if (i==n && sum[j]==k)   //到达第n行且放置棋子数达到k
				ans += dp[i][j];
		}
		printf("%d\n",ans);
	return 0;
}


Poj 3254 Corn Fields

题意:一个矩阵里有很多格子,每个格子有两种状态,可以放牧和不可以放牧,可以放牧用1表示,否则用0表示,在这块牧场放牛,要求两个相邻的方格不能同时放牛,即牛与牛不能相邻。问有多少种放牛方案(一头牛都不放也是一种方案)

思路:使用了滚动数组

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#define mod 100000000
using namespace std;

vector<int> t;   //保存每一行所有可能的放法

int dp[2][400];
int can[13];

bool check (int x)   //测连续两个1
{
	if (((x>>1)&x)==0)
		return true;
	return false;
}

void Init ()
{
	memset(dp,0,sizeof(dp));
	t.push_back(0);
	for (int i=1;i<(1<<12);i++)
		if (check(i))
			t.push_back(i);
}

int main ()
{
	Init();
	int n,m,i,j;
	scanf("%d%d",&n,&m);
	for (i=1;i<=n;i++)
	{
		int temp=0,x;
		for (j=1;j<=m;j++)
		{
			scanf("%d",&x);
			temp=temp*2+x;
		}
		can[i]=temp;   //能放的地方
	}
	
	int limit=1<<m,len=t.size();
	__int64 ans=0;
	for (i=0;i<len;i++)   //初始化第一行
	{
		if (t[i]>=limit)
			break;
		int now=t[i];
		if ((now|can[1])==can[1])  //该状态合法
            dp[1][i]=1;
	}
	for (i=2;i<=n;i++)
	{
		int k=i&1;
		memset(dp[k],0,sizeof(dp[k]));    //记得清空滚动数组
		for (j=0;j<len;j++)
		{
			if (t[j]>=limit)
				break;
			int now=t[j],s;
			if ((now|can[i])==can[i])
				for (s=0;s<len;s++)
				{
					if (t[s]>=limit)
						break;
					int pre=t[s];
					if ((can[i-1]|pre)==can[i-1] && (pre&now)==0) //(pre&now)==0表示上一行不与本行相邻
						dp[k][j]=(dp[k][j]+dp[k^1][s])%mod;
				}
		}
	}
	for (i=0;i<len;i++)
	{
		if (t[i]>=limit)
			break;
		ans=(ans+dp[n&1][i])%mod;
	}
	printf("%I64d\n",ans);
	return 0;
}


Poj 1185 炮兵阵地

思路:dp[i][j][k]表示第i行状态为j,第i-1行状态为k时的方案数。

状态转移方程:dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][t]+num[j]); 其中num[j]是j的二进制中1的个数

#include <cstdio>
#include <cstring>
#define max(a,b) ((a)>(b)?(a):(b))
 
int n,m;
char map[110][20],num[110],top;
int stk[70],can[110];
int dp[110][70][70];
 
bool Ok (int x)
{
	if (x&(x<<1))
		return false;
	if (x&(x<<2))
		return false;
	return true;
}

//计算一个整型数x的二进制中1的个数
int Cal (int x)
{	
	int cnt=0;
	while (x)
	{
		cnt++;
		x&=(x-1);
	}
	return cnt;
}

//找到所有可能的合法状态,最多60种
void Init ()
{
	top=0;
	int total=1<<m;
	for (int i=0;i<total;i++)
		if (Ok(i))
		{
			stk[++top]=i;
			num[top]=Cal(stk[top]);
		}
	memset(dp,0,sizeof(dp));
	memset(can,0,sizeof(can));
}

int main ()
{
	while (~scanf("%d%d",&n,&m))
	{
		Init ();
		int i,j,k,t;
		for (i=1;i<=n;i++)
			scanf("%s",map[i]+1);
		for (i=1;i<=n;i++)
		{
			can[i]=0;
			for (j=1;j<=m;j++)
				if (map[i][j]=='H')
					can[i]+=(1<<(j-1));  //不能放的地方置1
		}

		for (i=1;i<=top;i++)
			if (stk[i]&can[1])
				continue;
			else
				dp[1][i][1]=num[i];

		for (i=2;i<=n;i++)
			for (j=1;j<=top;j++)
			{
				if (stk[j]&can[i])
					continue;
				for (k=1;k<=top;k++)
				{
					if (stk[j]&stk[k])
						continue;
					for (t=1;t<=top;t++)
					{
						if (stk[j]&stk[t])
							continue;
						dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][t]+num[j]);
					}
				}
			}
		int ans=0;
		for (i=1;i<=n;i++)
			for (j=1;j<=top;j++)
				for (k=1;k<=top;k++)
					ans = max(ans,dp[i][j][k]);
		printf("%d\n",ans);
	}
	return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: