UVA1602 Lattice Animals 回溯+set判重

来源:互联网 发布:网络直销不合法 编辑:程序博客网 时间:2024/05/16 14:18

按照题意,只要我们能找出所有n连块,然后再判断能不能放进w*h的方格中就行了。找出所有n连块可以用回溯法,从k连块搜索,通过在其周围放一个方块,从而得到k+1连块,从而这样不断搜索下去直到n连块,搜索过程的判重就交给set了。因为题目中有多组输入,假如连续输入1000组相同的数据,我们都要一遍一遍的dfs吗?显然太复杂。所以我们可以事先把所有可能的输入数据对应的答案打表,这样输出就快的多了。在打表的时候又面临一个问题了,我们要得到所有的4连块,就要从1连块开始dfs,要得到所有的5连块,也要从1连块开始dfs,这样不是显得太笨拙了吗?同时我们写的这种最简单的枚举方法,使得每一种n连块都被枚举了很多次,所以上面那种笨拙的方法是肯定不能用的,必然会TLE。那么怎么做呢?假如我们要得到所有的5连块,我们就可以通过刚刚已经搜索的4连块得出,这样就简化了搜索过程。开始写代码面临的第一个问题就是如何表示n连块的状态,我们可以把n连块想成是n个坐标的集合,用set实现就可以了。下面是具体实现过程:

#include <iostream>#include <cstdlib>#include <cstdio>#include <cstring>#include <set>#define INF 0x3f3f3fusing namespace std;const int dx[] = {-1, 1, 0, 0};//方向数组const int dy[] = {0, 0, -1, 1};int n, w, h;int ans[15][15][15];//所有输入对应的答案struct cell//定义单元格{    int x, y;    cell(int a, int b)    {        x = a;        y = b;    }    cell(){}    bool operator <(const cell &rhs) const    {        if (x != rhs.x) return x < rhs.x;        return y < rhs.y;    }};typedef set<cell> poly;//n个坐标的集合也就是一个n连块set<poly> vis[15];//用来判重的set,vis[n]是被访问过的所有n连块的集合poly initial_normal(const poly &p)//将这个n连块标准化成左上角在(0,0)点{    poly goal;    int minx = INF, miny = INF;    for (cell it : p)    {        minx = min(minx, it.x);        miny = min(miny, it.y);    }    for (cell it : p)        goal.insert((cell){it.x - minx, it.y - miny});    return goal;}inline poly rotate(const poly &p)//顺时针旋转转90°,千万记住末尾返回的应该是标准化之后的答案{                                //因为有的n连块顺时针旋转90°之后出界了,需要将其标准化。    poly goal;                  //因为这个函数会被执行多次,用inline可以加快执行速度    for(poly::iterator i=p.begin(); i!=p.end(); i++)        goal.insert(cell(i->y, -i->x));    return initial_normal(goal);}inline poly flip(const poly &p)//将一个n连块沿x轴翻转,同时也要将答案标准化{    poly goal;    for(poly::iterator i=p.begin(); i!=p.end(); i++)        goal.insert(cell(i->x,-i->y));    return initial_normal(goal);}bool had_vis(poly p)//判断一个n连块是否访问过,没访问过就插入到vis里面,返回false{    int len = p.size();    for (int i=0; i<4; i++)    {        p = rotate(p);        if (vis[len].count(p))            return true;    }    p = flip(p);//将p翻转之后,再次顺时针旋转看出现过没有    for (int i=0; i<4; i++)    {        p = rotate(p);        if (vis[len].count(p))            return true;    }    vis[len].insert(p);    return false;}void dfs(const poly &p)//由当前k连块dfs到n连块{    if (p.size() == n)    {        had_vis(p);        return;    }    for (cell cur : p)    {        for (int d=0; d<4; d++)        {            cell next(cur.x + dx[d], cur.y + dy[d]);            //if(next.x >= 0 && next.x < w && next.y >= 0 && next.y < h && !p.count(next))            //刚开始这里是这样写的,最后答案错误,想了很久才发现。现在的目标不是dfs出            //一个可以装进w*h网格中的n连块,现在的目的是dfs出所有的n连块,所以不在乎能不能装进去            if (!p.count(next))            {                poly p_next = p;                p_next.insert(next);                dfs(p_next);            }        }    }}void print_table()//将所有可能的输入打表{    memset(ans, 0, sizeof(ans));    poly S;    S.insert(cell(0, 0));//得到1连块    vis[1].insert(S);    for (n=2; n<=10; n++)//首先搜索n = 10的情况,这样搜索一遍后,vis里面    {                   //就含有了所有大小的连通块。搜索k连块是借用的k-1连块的搜索结果。        for (poly it1 : vis[n - 1])            dfs(it1);    }    for (n=2; n<=10; n++)//对所有可能打表        for (w=1; w<=10; w++)            for (h=1; h<=10; h++)        {            int cnt=0;            for (poly it1 : vis[n])            {                int maxx=0,maxy=0;                for (cell it2 : it1)//寻找当前的连通块的最大的x,y                {                    maxx = max(maxx, it2.x);                    maxy = max(maxy, it2.y);                }                //能够放入w*h网格内的条件                if(min(maxx, maxy) < min(h, w) && max(maxx, maxy) < max(h, w))                    cnt++;            }            ans[n][w][h]=cnt;        }}int main(){    print_table();    while (scanf("%d%d%d", &n, &w, &h)==3)    {        if (n == 1) {printf("1\n");continue;}//1的时候特判        printf("%d\n", ans[n][w][h]);    }    return 0;}




0 0
原创粉丝点击