UVA1602 Lattice Animals 回溯+set判重

按照题意,只要我们能找出所有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 0x3f3f3f

using 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
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值