首页 > 其他分享 >Mondriaan's Dream 【POJ2411】 题解

Mondriaan's Dream 【POJ2411】 题解

时间:2023-04-08 17:14:50浏览次数:35  
标签:cnt 判断 题解 Mondriaan 一行 POJ2411 枚举 当前 木条

Mondriaan's Dream 【POJ2411】 题解

——By Zy

注:原题中的 \(h,w\) 在本文中使用 \(n, m\) 代替。

一. 题意分析:

题目要求给定一个一定大小的 矩形 棋盘,求出使用 \(1\times 2\) 大小的木条填充一共有多少种方案。

  • 读题,发现数据范围 \((1\le h,w\le11)\),考虑使用状压 DP 算法,于是可以用 0 和 1 分别表示某个格子放置或未放置木条。
  • 观察每一格的状态,可以发现存在如下两种情况:
  • 一个 \(1\times 2\) 的连通 “0” 块,说明这块区域内可以放置一根木条。
  • 一个 \(1\times 1\) 的单独 “0” 块,说明这个区域可以放置一个木条的一半,另一半需要伸到另一行或另一列。

二. 合法性预处理:

显然,在判断某一行的状态是否可行时,我们需要考虑的是两点:

  1. 一个 \(1\times 2\) 的 "0" 连通块。
  2. 一个 \(1\times 1\) 的单独 "0" 块。
    对于上述两种情况,我们发现:
  3. 第一种只与当前行有关,即无论上一行或下一行是什么状态,都不会影响这两格是否可以放置木条。
  4. 对于第二种情况,我们需要判断上一行的对应格处是否伸出来了木条,例如当判断情况 "11011" 时,假若上一行为 "11111",那么就可行,因为当前行 "0" 空格处刚好可以被上一行对应格处伸出来的木条覆盖。即若枚举当前行的第 \(i\) 格与上一行的第 \(i\) 格满足一 "0" 一 "1",那么当前行就可以 作为枚举到的上一行的下一行,注意这种可行性只存在于当前枚举到的两行之间。

于是,我们便可以预处理出以下两种数据:
1.判断某一行作为单独一行(即与前后两行没有共用的木条)时是否可行。根据前文的分析,我们只需要判断当前枚举到行的二进制位下,是否满足所有连通的 "0" 都恰好有偶数个,这样就可以刚好放置若干个木条。 映射到代码里,我们只需要依次取出某一位判断是否为 0. 注意最后由于如 "11110000" 的情况,一串 "0" 是直接结束的,需要在循环结束后再次判断( 的教训)。

for (int i = 0; i < (1 << n); i ++) {
	int cnt = 0;
	bool flag = true;
	for (int j = 0; j < n; j ++) {
		if ((i >> j) & 1) { // 取出每一位并判断是否为 0
			if (cnt & 1) { // 前一段连通的 0 为奇数个,则不合法 
				flag = false;
				break;
			}
			cnt = 0;
		} else { // 当前取出位为 0,累加长度
			cnt++;
		}
	}
	if (cnt & 1) { // 再次判断(血与泪的教训)
		flag = false;
	}
	book[i] = flag;
}

2.判断某两行的状态是否可以使这两行连续存在且合法。前文提到过,假若某两行的相同位置 一 "0" 一 "1",则合法。于是可以用 C++ 按位与运算(&)来进行 判断,因为这两种合法情况的按位与结果都为 0. 那如何存储预处理结果呢?由于两行连续存在,于是想到使用 std::vecotor 储存,如果当前行可以接在上一行之后,那么就把当前行的情况加入上一行的情况所在的 vecotor. 重要的是,存在一个比较大的误区,就是当前行与上一行结合后,当前行可能还存在空格。于是还要判断当前行与上一行结合后,当前行剩下的空格,是否可以凭借仅仅放若干个完整的木条来填满。 对应的 C++ 位运算:

  1. 判断两行是否可以相连: if((i & j) == 0)
  2. 两行结合后的当前行:i | j。解释:只要上一行的某个位置存在木块且当前行的该位置不存在,那么当前行的该位置就可以被覆盖。
vector<int> Sits[1 << 12];
...
for (int i = 0; i < (1 << n); i ++) {
	Sits[i].clear();
	for (int j = 0; j < (1 << n); j ++) {
		if ((i & j) == 0 && book[i | j]) {
			Sits[i].push_back(j);
		}
	}
}

三. 方程的定义与 DP 转移:

由于 DP 转移的原理,方程中应该包含以下要素:

  1. 行号(枚举顺序)
  2. 压缩后的状态
  3. 方案数

于是我们可以定义 \(f_{i, j} = x\) 表示第 \(i\) 行的状态为 \(j\) (假设前 \(i - 1\) 行都已确定)时的方案数。由于一共有 \(m\) 行,我们可以将数组略定大一点,最终求到 \(f_{m + 1, 0}\) 的值,便是包括到 \(m\) 行的方案总数。
截止到上一行的方案数即为上一行所有合法的情况数总和。由于上一行是否合法与上上行也有关系,所以实际上应该要枚举三行(三重循环),当前行,上上行,以及合法的可以接在上上行之后的情况总数。设枚举当前行为 \(i\),上上行为 \(j\),枚举到的上一行为 \(k\),则显然有:

\[f_{i,j} = \sum{f_{i-1,k}} \]

由于 -1 行不存在,所以第 0 行实际只有一种情况 \(f_{0,0}=1\),这是需要在 DP前进行赋值的。

最后:多测不清空,万事一场空。

#include <bits/stdc++.h>

#define int long long
#define endl '\n'

using namespace std;

int n, m;
int f[12][1 << 12];
bool book[1 << 12];
vector<int> Sits[1 << 12];

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	while (cin >> n >> m && n && m) {
		for (int i = 0; i < (1 << n); i ++) {
			int cnt = 0;
			bool flag = true;
			for (int j = 0; j < n; j ++) {
				if ((i >> j) & 1) {
					if (cnt & 1) {
						flag = false;
						break;
					}
					cnt = 0;
				} else {
					cnt++;
				}
			}
			if (cnt & 1) {
				flag = false;
			}
			book[i] = flag;
		}
		for (int i = 0; i < (1 << n); i ++) {
			Sits[i].clear();
			for (int j = 0; j < (1 << n); j ++) {
				if ((i & j) == 0 && book[i | j]) {
					Sits[i].push_back(j);
				}
			}
		}
		memset(f, 0, sizeof f);
		f[0][0] = 1;
		for (int i = 1; i <= m; i ++) {
			for (int j = 0; j < (1 << n); j ++) {
				for (auto k : Sits[j]) {
					f[i][j] += f[i - 1][k];
				}
			}
				
		}
		cout << f[m][0] << endl;
	}

	return 0;
}

标签:cnt,判断,题解,Mondriaan,一行,POJ2411,枚举,当前,木条
From: https://www.cnblogs.com/ZyIOLO/p/17298806.html

相关文章

  • ECE 5101/CSE 5463 问题解答
    ECE5101/CSE5463,Spring2023Due:Apr.811:59pm,2023onCarmenHomeworkAssignment#4LateSubmissionNOTAcceptedHomeworkAssignment#41.(20points)InanunslottedALOHAsystem,thepacketarrivaltimesofallusersformaPoissonprocesshavingarate......
  • ARC130D ZigZag Tree 题解
    题目链接考虑这棵树在满足条件下是什么样子的?我们发现如果对于一棵树黑白染色,白色表示周围的点大于自身,黑色的点反之,是满足条件的。同时,将黑白点反色也是满足条件的。我们考虑进行\(\text{dp}\),设\(dp_{i,j,0/1}\)表示以点\(i\)为根的子树,\(i\)点权值的排名是\(j\),且......
  • 【牛客小白月赛70】A-F题解【小d和超级泡泡堂】【小d和孤独的区间】【小d的博弈】【小
    比赛传送门:https://ac.nowcoder.com/acm/contest/53366难度适中。......
  • Edu Round 板刷计划 4. Educational Codeforces Round 4 题解
    ChangeLog:2023.04.06开坑.A-TheTextSplitting弱智题.枚举分出来多少个长度为\(p\)的串,则能计算出长度为\(q\)的串有多少个,若合法则直接输出即可.无解输出-1.Samplesubmission.B-HDDisOutdatedTechnology比A还弱智.直接记录每个数的位置,然后模拟一......
  • [ARC127D] Sum of Min of Xor 题解
    先把\(i\)对\(j\)的约束去掉。没有\(\min\)的情况是trival的,发现瓶颈在于如何比较两个数之间的大小。可以发现,对两个二进制数,我们本质上是想要找到它们第一个不同的位置。于是考虑从最高位开始,将\((a_i,b_i)\)按最高位分组为\((0,0),(0,1),(1,0),(1,1)\)四组。每组内......
  • 【题解】P4898 [IOI2018] seats 排座位
    思路线段树。题意可以转化成每次判定有多少个前缀满足所有结点构成矩形。首先排除确定矩阵坐标再数答案的做法,因为太难。所以考虑如何对前缀进行判定。一个简单的想法是维护前\(i\)个点中\(x,y\)坐标的最值,但这样只能暴力看矩阵中的所有元素,跑得很慢。不妨思考一下合法......
  • 【题解】CF472G Design Tutorial: Increase the Constraints
    《正解分块+FFT跑1min,__builtin_popcount暴力跑10s》《没人写正解,CF也不卡》思路正解:分块+FFT乱搞:__builtin_popcount首先我们知道哈明距离可以用一种\(O(|字符集||S|)\)的算法求。具体考虑枚举字符集中的每一个字符,将两个串中是该字符的位置看作\(1\),不是该字......
  • 【题解】臭气弹
    用次数乘上\(P/Q\)来构建增广矩阵,进行高斯消元。在算出每个点被摧毁的概率与所有点的期望出现次数。由于每个点爆炸概率相同,所以每个点被摧毁的概率就是这个点的期望出现次数\(/\)所有点的期望出现次数。#include<bits/stdc++.h>usingnamespacestd;constintN=311;......
  • 荣耀magicbook安装Linux系统boot fail问题解决
    偶然网上冲浪,发现了Debian系的kalilinux有点意思,刚好手边有一台不怎么用的荣耀magicbook,于是准备装个双系统好不容易下完了kali的镜像,使rufus写入了U盘但是在安装过程中怎么安装都显示bootfail,切换了n个版本的Linux系统,发现还是这样,但是实测Debian11是可以进入引导项的最后所......
  • CF1810E 题解
    一、题目描述:给你一个n个点,m条边的无向图,点带权,起点可任意选择。每走过一个新的点,你的能力值会+1。一开始你的能力值为0。你只能经过点权小于等于你能力值的点。每条边,每个点都可以经过无限次,问能否走遍整个图。如果可以,输出"YES"。否则输出"NO"。......