【紫书】网格动物 UVA1602

题目描述

输入n,w,h,求能放在w*h网格里的不同的n连块的个数(注意:平移、旋转、翻转后相同的算作同一种)。
具体题目
提交处:洛谷、vjudge、UVA官网等,都可以提交的。

题目思路

此题难点就在于判重,也就是题目所强调的 平移、旋转、翻转后相同的算作同一种 这就很无奈,运用一般的暴力做不了,怎么办呢,所以就得想办法去解决这个判重的问题。

对于平移
我们可以将我们的图形转化为矢量图形使之标准化。这样就解决了平移重复的问题。
每次找到图形所有坐标的最小x,y值,另每一个图形中的坐标减去(x, y),这样就使得图形标准化了。
几何变换为:
(x, y) => (x - minX, y - minY)

对于旋转
因为是由正方形组成的一个图形,他每次旋转也只会旋转90°的倍数,所以我们就将图形的每一个点旋转90°。
几何变换为:
(x, y) => (y, -x)

对于翻转
有对x翻转, 也有对y进行翻转,两种都可以,应为x, y 翻转的图形种类都是一样的。
几何变换为:
(x, y) => (x, -y)

解决完平移、旋转、翻转的模拟以后就是生成图形、储存图形和判重了。

生成图形
让每个图形的点进行扩展,如果扩展之后没有重复的样式,那么这个就可以加入到我们的存储数据中。

储存图形
设置一个存储坐标的结构体,然后用集合去存储连在一起的坐标点,代表一个图形。
一个图形还是散的,用图形组成的方块数去分类,用集合组去分类,下标代表的就是该集合中图形组成的块状。

判重
用集合存储,只要判断该图形有没有出现过就行。

代码详细讲解

#include<bits/stdc++.h>
using namespace std;

#define _for(i, a, b) for (int i = (a); i < (b); ++i) 
// 简化代码
const int N = 11;

struct Cell {
	int x, y;

	Cell(int x, int y) : x(x), y(y){}

	bool operator < (const Cell& c) const {
		return x < c.x || (x == c.x && y < c.y);
	}
};// 表示坐标点

typedef set<Cell> CellSet; // 储存坐标的集合类型定义
set<CellSet> blocks[N]; // 储存集合类型的集合
int dx[] = { -1, 0, 1, 0 }, dy[] = { 0, 1, 0, -1 }; // 方向定义
int ans[N][N][N]; // 储存答案, 打表

inline CellSet init(const CellSet& shape) { // 让图形标准化
	int minX = (*shape.begin()).x, minY = (*shape.begin()).y;
	for (const auto& p : shape) {
		minX = min(minX, p.x);
		minY = min(minY, p.y);
	}

	CellSet s;
	for (const auto& p : shape) s.insert(Cell(p.x - minX, p.y - minY));
	return s;
}

inline CellSet rotate(const CellSet& shape) { // 图形旋转
	CellSet s;
	for (const auto& p : shape) s.insert(Cell(p.y, -p.x));
	return init(s);
}

inline CellSet flip(const CellSet& shape) { // 图形翻转
	CellSet s;
	for (const auto& p : shape) s.insert(Cell(p.x, -p.y));
	return init(s);
}

void Insert(const CellSet& shape, const Cell& cell) { // 插入图形
	CellSet block = shape; // 复制一遍数据
	block.insert(cell); // 加入一个坐标, 观察是否符合有过该题型
	block = init(block);  // 修改之后不要忘记初始化

	int n = block.size(); // 根据图形坐标个数去判断他在总集合的哪一个类型中
	_for(i, 0, 4) {
		if (blocks[n].count(block)) return; // 旋转,查看是否存在过
		block = rotate(block);
	}

	block = flip(block); // 翻转

	_for(i, 0, 4) {
		if (blocks[n].count(block)) return;// 旋转,查看是否存在过
		block = rotate(block);
	}
	blocks[n].insert(block); // 没有重复的图形,插入到总数据中
}

void Generate() {
	CellSet cellset;
	cellset.insert(Cell(0, 0));
	blocks[1].insert(cellset);

	_for(i, 2, N)  // 代表由i个坐标组成的情况
		for (const auto& shape : blocks[i - 1])  // 代表每个连通块
			for (const auto& cell : shape)  // 代表每个联通块中的每一个坐标
				_for(j, 0, 4) {
					Cell c(cell.x + dx[j], cell.y + dy[j]); // 进行拓展
					if (!shape.count(c)) Insert(shape, c); // 如果没有出现过,进行插入操作
				} 

	_for(n, 1, N) _for(w, 1, N) _for(h, 1, N) { // 打表
		int cnt = 0;
		for (const auto& shape : blocks[n]) {
			int max_x = 0, max_y = 0;
			for (auto& cell : shape) {
				max_x = max(max_x, cell.x);
				max_y = max(max_y, cell.y);
			}

			if (min(max_x, max_y) < min(w, h) && max(max_x, max_y) < max(w, h)) ++cnt;
		}
		ans[n][w][h] = cnt;
	}
}

int main() {
#ifdef LOCAL
	freopen("data.in", "r", stdin);
#endif // LOCAL
	Generate(); // 打表

	int n, w, h;
	while (~scanf("%d%d%d", &n, &w, &h)) printf("%d\n", ans[n][w][h]); // 输出结果
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值