您的位置:首页 > 其它

UVA1602 Lattice Animals 回溯+set判重

2016-10-16 14:15 10 查看
按照题意,只要我们能找出所有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连块的集合

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
)
{
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
[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
[w][h]);
}
return 0;
}



                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息