【AcWing】327.种玉米(状态压缩+DP)

【AcWing】327.种玉米(状态压缩+DP)

描述

农夫约翰的土地由 M×N

个小方格组成,现在他要在土地里种植玉米。

非常遗憾,部分土地是不育的,无法种植。

而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。

现在给定土地的大小,请你求出共有多少种种植方法。

土地上什么都不种也算一种方法。
输入格式

第 1
行包含两个整数 M 和 N

第 2…M+1
行:每行包含 N 个整数 0 或 1,用来描述整个土地的状况,1 表示该块土地肥沃,0

表示该块土地不育。
输出格式

输出总种植方法对 108

取模后的值。
数据范围

1≤M,N≤12

输入样例:

2 3
1 1 1
0 1 0

输出样例:

9

思路&感受

状态压缩曾经尝试入行ACM时知道有这么回事,最近刷力扣发现有一类题可以用此类方法处理,想着既然没有学习过此类题解法便来试试

预备知识

状态压缩基础:位运算
此处的位运算是指将两个十进制的数在二进制下进行的,比如3的二进制是11,2的二进制是10,则11&10=10=2
状态压缩的一些应用:

  • 判断一个数字x二进制下第i位是不是等于1。
    方法:if(((1<<(i−1))&x)>0)
    将1左移i-1位,相当于制造了一个只有第i位上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0,说明x第i位上是1,反之则是0。

  • 将一个数字x二进制下第i位更改成1。
    方法:x=x|(1<<(i−1))
    证明方法与1类似,此处不再重复证明。

  • 把一个数字二进制下最靠右的第一个1去掉。
    方法:x=x&(x−1)
    说实话移位这块之前上汇编时候就没太学好,着实是看了好久,,,

题解

题目意思很明确,玉米田中的玉米前后左右是不可以相邻的,这个背景和八皇后类似。但相比八皇后还加了限制:存在肥田和荒地
在此考虑将每一行抽象成一个01串(二进制),通过位运算来判断在某块田上是否可以进行玉米种植
对于动态规划问题,我们先确定状态转移方程:令 d p ( i , j ) dp(i,j) dp(i,j)表示在第 i i i地块下的状态(是否种植),
我们假设1表示可以种植,0不可以种植,现在有两相邻行地块如下:
S 1 = 1010 , S 2 = 0101 S_1=1010,S_2=0101 S1=1010,S2=0101
这两行合法吗?合法,我们将两地块与上,得0。
我们再枚举其他不合法的地块可以看出,当 S i a n d S i + 1 ! = 0 S_i and S_{i+1}!=0 SiandSi+1!=0时,是不合法的
针对不可以种植的土地,我们再令 g ( i ) = = 1 g(i)==1 g(i)==1,此时只要有状态 s a n d g ( i ) = = 0 sandg(i)==0 sandg(i)==0

感受

毕竟不是ACMer,第一次入门此类题目真的有点难。这篇笔记也是自己敲了很久才写出来的,很多东西还是要以后慢慢补

代码

#include<iostream>
#include<vector>
using namespace std;

const int N = 13, Mod = 100000000;
vector<int> status, head[1 << N];
int n, m, x, dp[14][1 << N], g[N];

bool check(int x) {
	//有没有相邻的1
	return !(x & x >> 1);//和移位进行与运算
}

inline void solve() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> x;
			g[i] += (!x << (j - 1));//位运算的应用,表荒废的土地
		}
	}
	for (int i = 0; i < (1 << m); i++) {
		if (check(i))
			status.push_back(i);//不存在种植左右相邻的玉米
	}
	for (int i = 0; i <= status.size(); i++) {
		for (int j = 0; j < status.size(); j++) {
			if (!(status[i] & status[j]))
				head[i].push_back(j);//对应状态
		}
	}
	dp[0][0] = 1;
	for (int i = 1; i <= n + 1; i++) {
		for (int a = 0; a < status.size(); a++) {
			if (status[a] & g[i])
				continue;
			for (int b = 0; b < head[a].size(); b++) {
				//状态转移
				dp[i][a] = (dp[i][a] + dp[i - 1][head[a][b]]) % Mod;
			}
			
		}
	}
	cout << dp[n + 1][0] << endl;
}

int main() {
	solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值