puzzle(0411)《骨牌》覆盖、染色

这篇博客探讨了不同尺寸的棋盘覆盖问题,包括2格、3格、4-5格骨牌的平铺策略。通过分析奇偶性和使用特定形状的骨牌,如L型骨牌,解决了一些经典算法谜题。同时,文章介绍了染色技巧在解决覆盖问题中的应用,揭示了染色的本质和不同染色方式的影响。这些问题的解答涉及到了数学的巧妙运用和逻辑推理。

目录

一,2格骨牌覆盖问题

1,剪残了的棋盘

2,算法谜题 12 平铺多米诺问题

二,3格骨牌覆盖问题

1,算法谜题 78 直三格板平铺

2,L骨牌覆盖问题

三,4-5格骨牌覆盖问题

1,算法谜题 38 四格骨牌平铺问题

2,智力游戏 打包1

3,智力游戏 打包2

4,智力游戏 打包3

5,日历拼图

6,立方体拼图

四,染色技巧

1,染色的本质

2,4格覆盖问题

3,常见染色方式


一,2格骨牌覆盖问题

1,剪残了的棋盘

不知道为什么,想起了KMP。。。

言归正传,既然待匹配项为1*2的长方形,那么可以把棋盘哈希到整数,分析奇偶性。

这个题目很简单,因为有个很简单的哈希函数:f=x+y,即国际象棋的棋盘。

所以答案是不能。

如果,减掉一个白格子和一个黑格子,那又能不能覆盖呢?

答案是能。

把64个格子弄成1个环

减掉2个格子之后,环变成2个线段。

因为2个格子一白一黑,所以2个线段的长度都是偶数。

所以2个线段都可以用1*2覆盖。

2,算法谜题 12 平铺多米诺问题

        能否用单位长度 2*1 的多米诺牌将 8*8 的方格阵铺满? 里面不包含由两张 2*1 多米诺并行排列而成的 2*2 的正方形。
        答案: 所要求的平铺方法不可能实现。

        本题可用反证法证明。假设这样的平铺方法可以实现, 由于方格板是对称的, 我们假设它的左上角是被如图 4.6 所示的一块横置的多米诺牌 1 所覆盖, 那么, 第二行第一列的方格必然是被一块坚直排放的多米诺牌覆盖, 因此, 同行第二列会是一块横放的多米诺牌。按照这个思路推演下去, 我们会得到图 4.6 所示的平铺图。 在多米诺牌 13 之下放一块横置的多米诺牌, 变成了唯一的选择, 这与没有两张多米诺牌并行排列形成 2*2 的正方形的条件矛盾。

二,3格骨牌覆盖问题

1,算法谜题 78 直三格板平铺

 一个直三格板是一个 3 *1 的瓦片平铺。很显然, 只要 n 能够被 3 整除, 任何人都能够通过直三格板平铺成一个 n *n 的正方形。那么, 对于一个大于 3 而又不能被 3 整除的 n 来说, 是否能够利用直三格板和一个叫做单格板的 1 * 1 瓦片平铺, 来构成一 个 n *n 的正方形?

2,L骨牌覆盖问题

边长为2^k的正方形,少了一个格子,如何用L型骨牌覆盖?

可以采用分治法:

三,4-5格骨牌覆盖问题

1,算法谜题 38 四格骨牌平铺问题

2,智力游戏 打包1

智力游戏

 

这一关,就是要把所有的木块不重叠的放进正方形。

因为正方形的边长为8,所以有64个格子。

对于木块,除了正方形之外,另外12个都有5个格子,所以一共也是64个格子。

所以最后应该是恰好铺满才对。

答案:

3,智力游戏 打包2

 

规则同上,但是木块不一样,这一关,木块是11个一样的木块,所以最后会有9个空格子。

答案:

4,智力游戏 打包3

5,日历拼图

https://blog.csdn.net/nameofcsdn/article/details/123765415

6,立方体拼图

代码V1:


class DancingLink
{
public:
	vector<int>rows;//覆盖选中的行,值的范围是从1到m
	DancingLink(int m, int n, int maxNum)
	{
		this->m = m, this->n = n, maxNum += n + 1;
		rhead.resize(m + 1), nums.resize(n + 1);
		row.resize(maxNum), col.resize(maxNum);
		up.resize(maxNum), down.resize(maxNum), lef.resize(maxNum), rig.resize(maxNum);
		sc.resize(m), rows.resize(m);
		for (int i = 0; i <= n; i++)
		{
			up[i] = i, down[i] = i;
			lef[i] = i - 1, rig[i] = i + 1;
			row[i] = 0, col[i] = i, nums[i] = 0;
		}
		lef[0] = n, rig[n] = 0, nums[0] = INT_MAX;
		key = n;
		for (int i = 0; i <= m; i++)rhead[i] = 0;
	}
	void push(int r, int c)//新增坐标在(r,c)的一个节点
	{
		row[++key] = r, col[key] = c;
		up[key] = c, down[key] = down[c];
		up[down[c]] = key, down[c] = key;
		if (rhead[r] == 0)rhead[r] = lef[key] = rig[key] = key;
		else
		{
			lef[key] = rhead[r], rig[key] = rig[rhead[r]];
			lef[rig[rhead[r]]] = key, rig[rhead[r]] = key;
		}
		nums[c]++;
	}
	bool dfs()
	{
		while (true) {
			if (rig[0] == 0) {
				rows.resize(rowsid);
				return true;
			}
			int c = min_element(nums.begin(), nums.end()) - nums.begin();
			del(c);
			while (c = down[c]) {
				if (c > n)break;
				reback(col[c]);
				c = sc[--scid];
				rowsid--;
				for (int j = rig[c]; j != c; j = rig[j])reback(col[j]);
			}
			sc[scid++] = c;//记录选中id
			rows[rowsid++] = row[c];
			for (int j = rig[c]; j != c; j = rig[j])del(col[j]);
		}
		return false;
	}
private:
	inline void del(int c)//删除第c列的所有元素和他们所在行的所有元素
	{
		lef[rig[c]] = lef[c], rig[lef[c]] = rig[c];
		for (int i = down[c]; i != c; i = down[i])
			for (int j = rig[i]; j != i; j = rig[j])
				down[up[j]] = down[j], up[down[j]] = up[j], nums[col[j]]--;
		nums[c] = INT_MAX;
	}
	inline void reback(int c)//完全回退del操作
	{
		lef[rig[c]] = rig[lef[c]] = c, nums[c] = 0;
		for (int i = down[c]; i != c; i = down[i]) {
			for (int j = rig[i]; j != i; j = rig[j])
				down[up[j]] = up[down[j]] = j, nums[col[j]]++;
			nums[c]++;
		}
	}
private:
	int m, n, key;
	vector<int>row, col;//每个节点的行,列
	vector<int>rhead;//每行第一个节点的id
	vector<int>up, down, lef, rig;//每个节点上下左右的节点id
	vector<int>nums;//每一列的元素个数
	vector<int>sc;
	int scid = 0, rowsid = 0;
};

struct Grid
{
	int r, c, h;
	Grid(int rr, int cc, int hh) :r(rr), c(cc), h(hh) {}
	bool operator<(const Grid& g) const
	{
		if (h == g.h) {
			if (r == g.r)return c < g.c;
			return r < g.r;
		}
		return h < g.h;
	}
};
struct Block //一个块的所有形态
{
	vector<vector<Grid>>grids;
	Block(vector<vector<Grid>>g, int maxDr, int maxDc, int maxDh, const map<Grid, int>& m)//块的所有形态在最小位置包含的格子,最大偏移,待覆盖区域包含的格子
	{
		this->m = m;
		for (auto& g2 : g) {
			for (int i = 0; i < maxDr; i++)for (int j = 0; j < maxDc; j++)for (int k = 0; k < maxDh; k++) {
				for (auto& x : g2)x.r += i, x.c += j, x.h += k;
				if (inBoard(g2))grids.push_back(g2);
				for (auto& x : g2)x.r -= i, x.c -= j, x.h -= k;;
			}
		}
	}
	Block() {}
private:
	map<Grid, int>m;
	bool inBoard(vector<Grid>& g)
	{
		for (auto& x : g)if (!m[x])return false;
		return true;
	}
};

vector<vector<Grid>> Cover(vector<Block>blocks, map<int, int>ids, map<Grid, int>& mg)//所有块,不限数量的块的id, 待覆盖区域包含的格子编号为1,2,3...
{
	int m = 0, maxNum = 0;
	for (auto& block : blocks) {
		m += block.grids.size();
		maxNum += block.grids.size() * (block.grids[0].size() + 1);
	}
	DancingLink d(m, mg.size() + blocks.size() - ids.size(), maxNum);
	int r = 0, c = mg.size();
	map<int, int>mrow;
	for (int i = 0; i < blocks.size(); i++) {
		mrow[r + 1] = i + 1;
		for (auto& grids : blocks[i].grids) {
			++r;
			for (auto& g : grids)d.push(r, mg[g]);
			if (ids[i] == 0)d.push(r, ++c);
		}
	}
	d.dfs();
	vector<int> rows = d.rows;
	vector<vector<Grid>> ans;
	for (auto row : rows) {
		int id = 0;
		while (!mrow[row])row--, id++;;
		ans.push_back(blocks[mrow[row] - 1].grids[id]);
	}
	return ans;
}

int r, c, h, blockNum; //自定义行列数,块数
map<Grid, int> ng, mg;  //ng是自定义挖掉的格子,mg是有效格子
vector<Block>blocks;//自定义每个块的所有形态在最小位置包含的格子
map<int, int>ids;

vector<Grid> rotate(vector<Grid>& g)
{
	int maxRow = 0, t;
	for (auto& gi : g)maxRow = max(maxRow, gi.r);
	for (auto& gi : g)t = gi.c, gi.c = maxRow - gi.r, gi.r = t;
	return g;
}
vector<Grid> rotate2(vector<Grid> g)
{
	int maxH = 0, t;
	for (auto& gi : g)maxH = max(maxH, gi.h);
	for (auto& gi : g)t = gi.c, gi.c = maxH - gi.h, gi.h = t;
	return g;
}

void init1()
{
	r = 6, c = 6, h = 6, blockNum = 1;
	ng.clear(), mg.clear();
}
void init2()
{
	vector<Grid>v1 = { {0,0,0},{0,1,0},{0,2,0},{1,1,0} };
	vector<Grid>v2 = { {0,0,0},{0,1,0},{0,2,0},{0,1,1} }, v3 = rotate2(v2), v4 = rotate2(v3), v5 = rotate2(v4);
	blocks[0] = Block{ { v1,rotate(v1),rotate(v1),rotate(v1),v2,rotate(v2),v3,rotate(v3),v4,rotate(v4),v5,rotate(v5)}, r, c,h, mg };
	ids[0] = 1;
}

void solve()
{
	init1();
	int id = 0;
	for (int i = 0; i < r; i++)for (int j = 0; j < c; j++) for (int k = 0; k < h; k++) {
		if (ng[Grid{ i, j ,k }] == 0)mg[Grid{ i, j,k }] = ++id;
	}
	blocks.resize(blockNum);
	init2();
	vector<vector<Grid>> grids = Cover(blocks, ids, mg);
	vector<vector<vector<int>>>v(r);
	for (int i = 0; i < r; i++) {
		v[i].resize(c);
		for (int j = 0; j < c; j++)v[i][j].resize(h);
	}
	for (int i = 0; i < grids.size(); i++) {
		for (auto& g : grids[i])v[g.r][g.c][g.h] = i + 1;
	}
	for (int i = 0; i < r; i++) {
		for (int j = 0; j < c; j++) {
			for (int k = 0; k < h; k++)cout << v[i][j][k] << " ";
			cout << endl;
		}
		cout << endl;
	}
}


int main()
{
	ios::sync_with_stdio(false);
	clock_t start, endd;
	start = clock();
	freopen("D:/ans.txt", "w", stdout);
	solve();
	endd = clock();
	double endtime = (double)(endd - start) / CLOCKS_PER_SEC;
	cout << "Total time:" << endtime << endl; //s为单位
	return 0;
}

输出:

1 1 1 2 2 2 
7 7 7 10 10 10 
20 20 20 23 23 23 
21 21 21 31 31 31 
24 21 37 36 31 32 
3 3 3 4 4 4 

5 1 8 9 2 11 
13 7 9 9 10 27 
25 20 35 9 23 27 
28 35 35 35 49 27 
24 37 37 36 32 32 
24 3 39 36 4 33 

5 5 8 8 11 11 
13 13 34 42 42 42 
25 25 34 34 42 27 
28 28 34 49 49 49 
24 30 37 36 48 32 
26 39 39 41 33 33 

5 14 8 43 44 11 
13 29 43 43 43 54 
25 29 29 50 54 54 
28 29 50 50 50 54 
30 30 40 48 48 48 
26 26 39 41 41 33 

14 14 14 44 44 44 
16 16 16 51 51 51 
18 16 17 52 51 53 
18 18 40 52 52 47 
18 30 40 52 47 47 
26 19 40 41 38 47 

6 6 6 12 12 12 
15 6 17 45 12 53 
15 15 17 45 45 53 
15 22 17 45 46 53 
22 22 22 46 46 46 
19 19 19 38 38 38 

Total time:0.953

对应答案:

 

 

 

 

更多解法:

参考拼接覆盖问题

四,染色技巧

1,染色的本质

染色的本质是把整个平面划分成若干个集合,根据每个部件在各个集合中的数量满足的条件,推出整体满足的数量规律。

当然,一般划分都是很有规律的。

2,4格覆盖问题

问题:用1个2*2和15个1*4可以覆盖8*8吗?

答案是不能,有2类染色法。

第一类,使得2*2为奇,1*4为偶

第二类,使得2*2为偶,1*4位奇

这2个图都强有力的说明了题目的答案是不能。

3,常见染色方式

棋盘染色法的分类与应用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值