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;
}
View Code

 

posted @ 2017-07-19 17:23  qwerity  阅读(398)  评论(0编辑  收藏  举报