题目描述
输入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;
}