UVa 1602 Lattice Animals (STL && 生成n连块 && 无方向形状判重)
题意 : 给定一个 w * h 的 矩阵,在矩阵中找不同n个连通块的个数(旋转,翻转,平移算作一种)
分析 : 这题的关键点有两个
① 生成n连块并且存储起来(因为题目是多测试用例,如果每一次都重新生成必将浪费很多时间)
② 判断是否生成了重复的n连块
存储 :首先先确定用什么结构来存储n连块的信息才能更好的进行操作,n连块是多个二维坐标的集合,所以这里先定义结构体 Cell {int x, int y} 表示坐标,然后将其塞到一个集合里面set<Cell>,n连块可能有不同的多种,所以这些n连块又构成了一个集合,再塞到一个集合里面set< set<Cell> >表示不同的多个n连块,n的取值是1~10,所以我们需要存储从1到10这些n连块的信息,可以用数组实现set< set<Cell> > poly[maxn+1] (maxn = 10),这样便解决了n连块的存储问题,而且对于每一个用例,都不用再重复计算了。
生成 :这里采用从1到10顺序生成n连块,对于当前n连块,可以从n-1连块拓展而来,假如现在要生产n连块,那可以取出某一n-1连块的每一个点向四个方向拓展,一旦拓展合法则进行判重,看看之前是否已经产生了形状一样的n连块
判重 :对于判重,这里麻烦的是有三种操作,即一个n连块经过旋转、平移、翻转后是一样的。首先来解决平移,如果能让所有的n连块都能想办法固定到一个地方,那无论是平移还是其他两个操作的判重都会变得简单,这里可以这样做,假设set<(x, y)>这个是当前这个n连块的坐标集合,我们找出其中最小的x和y=>minX, minY作为平移矢量,set<(x, y)>里面所有的点和(minX, minY)做减法,那么就能将这个n连块固定到(0, 0)附近,而且这个操作不受(x, y)所在的象限影响,对于固定之后的n连块只要对其坐标进行相应的操作即可判断,可以发现旋转四次和翻转两次后是一样的,所以这样做4次旋转=>翻转=>4次旋转,每一次都能产生新的坐标集合,在set中count一下就可以知道是否重复,而且不能进行两次一样的翻转中间是旋转,因为这样的话,原本“固定”的位置就会发生改变,如果没有重复,则加入到集合里面即可。
#include<bits/stdc++.h> #define ssit set< set<Cell> >::iterator #define sit set<Cell>::iterator using namespace std; struct Cell{ int x, y; Cell(int x=0, int y=0):x(x), y(y){}; bool operator < (const Cell & rhs) const{ if(x == rhs.x) return y < rhs.y; return x < rhs.x; } }; int dx[] = {1, -1, 0, 0}; int dy[] = {0, 0, 1, -1}; const int maxn = 10 + 1; set< set<Cell> > poly[maxn]; int ans[maxn][maxn][maxn]; inline set<Cell> Standard(const set<Cell> & temp) { int minX = temp.begin()->x, minY = temp.begin()->y; sit it; for(it=temp.begin(); it!=temp.end(); it++){///找出最小的(x, y) minX = min(minX, it->x); minY = min(minY, it->y); } set<Cell> ret; for(it=temp.begin(); it!=temp.end(); it++){///(minX, minY)平移到(0, 0),图形也跟着平移 ret.insert(Cell(it->x-minX, it->y-minY)); } return ret; } inline set<Cell> Rotate(const set<Cell> & temp) { set<Cell> ret; sit it; for(it=temp.begin(); it!=temp.end(); it++){ ret.insert(Cell(it->y, -it->x));///坐标的顺时针90°旋转是(x, y)=>(y, -x),逆时针则是(x, y)=>(-y, x) } return Standard(ret);///变化后当然也要固定一下 } inline set<Cell> Flip(const set<Cell> & temp) { set<Cell> ret; sit it; for(it=temp.begin(); it!=temp.end(); it++){ ret.insert(Cell(it->x, -it->y));///翻转(x, y)=>(y, x) } return Standard(ret); } inline void IF_add(const set<Cell>& s, const Cell& New) { set<Cell> temp = s;///定义临时变量将新点加入到刚刚传进来的n-1连块集合里面,构成一个n连块,接下来进行判重 temp.insert(New); temp = Standard(temp);///先根据平移矢量将(minX, minY)固定到(0, 0)位置 int n = temp.size(); for(int i=0; i<4; i++){///先进行四次旋转 if(poly[n].count(temp)) return ;///如果poly已经有了,则说明已经重复了 temp = Rotate(temp); } temp = Flip(temp);///翻转 for(int i=0; i<4; i++){///再来四次旋转 if(poly[n].count(temp)) return ; temp = Rotate(temp); } poly[n].insert(temp);///这个n连块是不和之前重复的,插入到poly[n]集合里面 } inline void Generate() { set<Cell> fir; fir.insert(Cell(0,0)); poly[1].insert(fir);///对于n=1的n连块,只有一种,将其定为我们刚刚说的固定地点(0, 0),然后开始拓展 for(int n=2; n<=10; n++){///顺序产生 for(ssit it=poly[n-1].begin(); it!=poly[n-1].end(); it++){///取出每一个n-1连块 for(sit it2=(*it).begin(); it2!=(*it).end(); it2++){///去除某一个n-1连块的某一个坐标点 for(int i=0; i<4; i++){///将坐标点进行四个方向的拓展 int x = it2->x + dx[i]; int y = it2->y + dy[i]; Cell temp(x, y); if(!it->count(temp)) IF_add(*it, temp);///如果拓展出来的点不和已有点重复则合法,进行判重处理 } } } } for(int n=1; n<maxn; n++){ for(int w=1; w<maxn; w++){ for(int h=1; h<maxn; h++){ int cnt = 0; for(ssit it=poly[n].begin(); it!=poly[n].end(); it++){ int maxX = 0, maxY = 0; for(sit it1=(*it).begin(); it1!=(*it).end(); it1++){ maxX = max(maxX, it1->x); maxY = max(maxY, it1->y); } if(min(maxX, maxY) < min(h, w) && max(maxX, maxY) < max(h, w))///如果将这个形状的n连块最短和在h, w中最小的平行 ///最长的和h, w最长的平行,如果可行,则说明放得下 cnt++; } ans[n][w][h] = cnt; } } } } int main(void) { Generate();///预处理所有的n连块信息 int h, w, n; while(~scanf("%d %d %d", &n, &w, &h)){ if(n==1) {puts("1"); continue; }///特判断n=1的情况 printf("%d\n", ans[n][w][h]); } return 0; }