POJ-3254(状压dp)

位运算相关知识:

1.’&’符号,x&y,会将两个十进制数在二进制下进行与运算,然后返回其十进制下的值。例如3(11)&2(10)=2(10)。

2.’|’符号,x|y,会将两个十进制数在二进制下进行或运算,然后返回其十进制下的值。例如3(11)|2(10)=3(11)。

3.’^’符号,x^y,会将两个十进制数在二进制下进行异或运算,然后返回其十进制下的值。例如3(11)^2(10)=1(01)。

4.’<<’符号,左移操作,x<<2,将x在二进制下的每一位向左移动两位,最右边用0填充,x<<2相当于让x乘以4。相应的,’>>’是右移操作,x>>1相当于给x/2,去掉x二进制下的最有一位。
常见应用:

1.判断数字x二进制下第i位是否是1

if((1<<(i-1))&x)

2.将数字x二进制下第i位改成1

x=x|(1<<(i-1))

3.去除数字x二进制下最右边的1

x=x&(x-1)

题目:

农场主John新买新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。

遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。

John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)

输入:

第一行:两个整数M和N,用空格隔开。

第2到第M+1行:每行包含N个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。

输出:

一个整数,即牧场分配总方案数除以100,000,000的余数。

样例:

输入:
2 3
1 1 1
0 1 0    
输出:
9

题目链接:3254 -- Corn Fields (poj.org)

思路:样例第一行为例有一下几种情况(1为种草,0为不种草)

编号状态
10 0 0
20 0 1
30 1 0
41 0 0
1 0 1

如果将状态都看为二进制的话,那么每种状态都有他对应的十进制数,那么就可以用一个数来表示一组数,这就是状态压缩。

编号二进制十进制
10 0 00
20 0 11
30 1 02
41 0 04
51 0 15

考虑完第一行后,我们应该来考虑第二行的状态,而且第二行和第一行的区别是在考虑第二行时需要考虑上下相邻的问题,因为没有两块草地有公共边也就是说各草地之间互不相连。

然后看第二行的样例,因为只有中间可以种地所以只有两种情况。

编号二进制十进制
10 0 0
20 1 02

我们通过分析得到一共有5+4=9种结果(第二行为0 0 0有五种 为0 1 0有四种)

通过对样例分析我们可以先找出没有相邻1的所有组合。

#include<iostream>
#include<algorithm>
using namespace std;
int state[600];
bool book(int i)//检测i中是否有连续1
{
	if (i & (i << 1))
		return false;
	else
		return true;
}
void inti()//将符合条件的组合放入state中
{
	top = 0;
	int nn = 1 << m;
	for (int i = 0; i < nn; i++)
		if (book(i))
			state[++top] = i;
}

之后可以考虑如何记录每行位置(可以反过来不能种记为1能种记为0这样之后只需要&运算即可排除组合)

#include<iostream>
#include<algorithm>
using namespace std;
int n,m;
int cur[20];
int main()
{
cin>>n>>m;
for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
			{
				cin >> x;
				if (!x)
					cur[i] += (1 << (m - j));
			}
}

写一个排除不能用组合的函数

#include<iostream>
#include<algorithm>
using namespace std;
int state[600];
int n,m;
int cur[25];
bool find(int i, int j)//注意i为state[x]
{
	if (i & cur[j])
		return false;
	return true;

先排除第一行不可用的组合


for (int i = 1; i <= top; i++)
		{
			if (find(state[i], 1))
				dp[1][i] = 1;

然后是除第一行之外不可用的组合

for (int i = 2; i <= n; i++)
			for (int j = 1; j <= top; j++)
			{
				if (!find(state[j], i))
					continue;
				for (int k = 1; k <= top; k++)
				{
					if (!find(state[k], i - 1))
						continue;
					if (state[j] & state[k])
						continue;
					dp[i][j] = (dp[i][j] + dp[i - 1][k]) % M;
				}
			}

通过以上操作我们可以得到所有可以出现的组合。最后只需要相加就可得到结果。

int ans = 0;
		for (int i = 1; i <= top; i++)
			ans += dp[n][i], ans %= M;
		cout << ans << endl;

AC代码

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int M = 100000000;
int dp[20][600];
int cur[20];
int n, m, top;
int state[600];
int x;
bool book(int i)
{
	if (i & (i << 1))
		return false;
	else
		return true;
}
void inti()
{
	top = 0;
	int nn = 1 << m;
	for (int i = 0; i < nn; i++)
		if (book(i))
			state[++top] = i;
}
bool find(int i, int j)
{
	if (i & cur[j])
		return false;
	return true;
}
int main()
{
	while (cin >> n >> m)
	{
		memset(dp, 0, sizeof dp);
		memset(state, 0, sizeof state);
		memset(cur, 0, sizeof cur);
		inti();
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
			{
				cin >> x;
				if (!x)
					cur[i] += (1 << (m - j));
			}
		for (int i = 1; i <= top; i++)
		{
			if (find(state[i], 1))
				dp[1][i] = 1;
		}
		for (int i = 2; i <= n; i++)
			for (int j = 1; j <= top; j++)
			{
				if (!find(state[j], i))
					continue;
				for (int k = 1; k <= top; k++)
				{
					if (!find(state[k], i - 1))
						continue;
					if (state[j] & state[k])
						continue;
					dp[i][j] = (dp[i][j] + dp[i - 1][k]) % M;
				}
			}
		int ans = 0;
		for (int i = 1; i <= top; i++)
			ans += dp[n][i], ans %= M;
		cout << ans << endl;
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值