您的位置:首页 > 其它

搜索 【uva1602】Lattice Animals (练习题7-14 网格动物)

2017-01-05 21:41 519 查看
7-14 网格动物(Lattice Animals uva 1602)

(animals.cpp,Time limit: 3.000 seconds)

题目描述:

给你一个w*h的网格,你可以用含有n个方块块去填充它,要求这n个方块连同,想知道一共有多少中填充方法(连通就不用解释了……吧)。

注:如果一个图形能通过另一个图形平移或者旋转得到,那么认为这两个图形是相同的。

(以上是我自己理解的,如果有不清晰的地方……可以看英文原版

举例子:

第一行:用5个方块的连通块填充2*4的方格,有5种填法。

第二行:用8个方块的连通块填充3*3的方格,有3种填法。



输入格式:

多组数据。

每行3个正整数n,w,h(1<=n<=10,1<=w,h<=n)

输出格式:

对于每组数据输出一行一个整数,代表有多少种填充方式。

样例输入:

5 1 4

5 2 4

5 3 4

5 5 5

8 3 3

样例输出:

0

5

11

12

3

题目分析:(搜索+set判重)

这种构造图形的题只能搜索了,重点是怎么搜,搜的时候要注意什么。

比较容易想到分层搜索,按照方块的数量来分层。

如果要是bfs搜到第10层就不要扩展新的状态。

如果dfs就迭代加深,搜到10层就回溯,我用的是深搜。

这道题其实没有什么技巧可言,在第一层的时候只有一种情况,就是一个方块,我们把这个图形储存下来,用它来扩展下一层的图形。

然后再用第二层得到的新图形来扩展第三层的图形,以此类推。

由于旋转平移和翻转之后如果图形相同答案不重复计算,所以需要判重,本来是想用hash的,但是本蒟蒻想不到一个把图形转成数字的好方法,所以就用set暴力判重了。

一个图形通过旋转和翻转会得到8种图案,我们可以用坐标的形式存储这个图形,并通过四次旋转90度,并且每次旋转之后翻转的方式得到8种图案。

因为平移也会影响坐标,所以我们统一把图案移动到第一象限,且使这个图案离坐标轴尽可能近,这样每种图案就只有八种表示方式。我们只把其中一个扔到set里,然后每次得到一个新的图案时,就用它的八种形式在set中跑一边判重,只要有一个在set出现过就可以减掉了。

这样我们保证所有的有用的图形都会扩展其它图形一次,而其实图形的总数只有6473,一个图形最多扩展出其它的图形也只有十几个,那么时间复杂度的级别在1e6左右,即使时限是1秒也跑出来了,都不用剪枝(也没有什么可以减的了)。

但是这道题是多组数据,如果你每次都搜一遍,把时间复杂度乘以300,那就很玄学了,所以你可以先把所有的数据都跑出来,存到一个数组里,最后一起输出结果。

代码如下(代码解释在代码后):

#include<cstdio>
#include<algorithm>
#include<set>
#include<iostream>
using namespace std;
const int INF=1000;
inline int Min(int x,int y) {return x<y?x:y;}
inline int Max(int x,int y) {return x>y?x:y;}

struct point{
int x,y;
bool operator == (const point c) const {return x==c.x && y==c.y;}
bool operator <  (const point c) const {return x<c.x || (x==c.x && y<c.y);}
point operator + (const point c)
{
static point ans;
ans.x=x+c.x;
ans.y=y+c.y;
return ans;
}
};
const point trans[4]={{0,1},{0,-1},{1,0},{-1,0}};

struct graph{
point spot[11];
int sz;

bool operator < (const graph c) const
{
if(sz!=c.sz) return true;
for(int i=0;i<sz;i++)
{
if(spot[i]<c.spot[i]) return true;
if(c.spot[i]<spot[i]) return false;
}
return false;
}

void translation()
{
sort(spot,spot+sz);
int xmin=INF,ymin=INF;
for(int i=0;i<sz;i++)
{
xmin=Min(xmin,spot[i].x);
ymin=Min(ymin,spot[i].y);
}
xmin=1-xmin,ymin=1-ymin;
for(int i=0;i<sz;i++)
{
spot[i].x+=xmin;
spot[i].y+=ymin;
}
return;
}

void turn()
{
for(int i=0;i<sz;i++)
{
swap(spot[i].x,spot[i].y);
spot[i].x=-spot[i].x;
}
translation();
return;
}

friend graph flip(graph c)
{
for(int i=0;i<c.sz;i++) c.spot[i].x=-c.spot[i].x;
c.translation();
return c;
}

void calculate(int &wide,int &height)
{
int minx=INF,maxx=-INF,miny=INF,maxy=-INF;
for(int i=0;i<sz;i++)
{
minx=Min(minx,spot[i].x);
maxx=Max(maxx,spot[i].x);
miny=Min(miny,spot[i].y);
maxy=Max(maxy,spot[i].y);
}
wide=maxx-minx+1;
height=maxy-miny+1;
}
}variable;

set<graph> V[11];
int ans[11][11][11];
int n,w,h;

void dfs(int c)
{
const graph cur=variable;
for(int i=0;i<c;i++)
for(int j=0;j<4;j++)
{
variable=cur;
point temp=variable.spot[i]+trans[j];

bool judge=true;
for(int k=0;k<variable.sz;k++)
if(temp==variable.spot[k]) { judge=false; break; }
if(!judge) continue;

variable.spot[variable.sz++]=temp;
for(int k=1;k<=4;k++)
{
variable.turn();
if(V[c+1].find(variable)!=V[c+1].end()) { judge=false;break;}
graph flipping = flip(variable);
if(V[c+1].find(flipping)!=V[c+1].end()) { judge=false;break;}
}
if(!judge) continue;

int wide,height;
variable.calculate(wide,height);
if(wide<height) swap(wide,height);
ans[c+1][wide][height]++;
V[c+1].insert(variable);
if(c==9) continue;
dfs(c+1);
}
return;
}

void get_ans()
{
variable.sz=1;
variable.spot[0].x=1;
variable.spot[0].y=1;
ans[1][1][1]++;
V[1].insert(variable);
dfs(1);

for(int i=1;i<=10;i++)
for(int j=1;j<=10;j++)
for(int k=1;k<=10;k++)
ans[i][j][k]+=ans[i][j-1][k]+ans[i][j][k-1]-ans[i][j-1][k-1];
for(int i=1;i<=10;i++)
for(int j=1;j<=10;j++)
for(int k=1;k<j;k++)
ans[i][k][j]=ans[i][j][k];
}

int main()
{
get_ans();
while(scanf("%d%d%d",&n,&w,&h)!=EOF)
printf("%d\n",ans
[w][h]);
return 0;
}


代码解释:

此下文段送给我的学弟学妹们(自认为代码可读性还可以,但是还是想写一写)。

结构体point代表点,x,y代表横纵坐标。

结构体graph代表图案,里面存储10个点代表具体的图案,sz代表这个图案的大小(即该图案由多少个方块构成)

因为graph要判重,所以要重定义 < 运算符。

translation是把所有的点排序并把整个图形平移到第一象限处。

turn是把这个图形逆时针旋转90度。

flip是把该图案翻转并返回当前图案。

calculate是计算当前图案的宽和高。

dfs:

枚举当前图案的每一个点并且枚举向哪个方向扩展。

第一次要判断是否这个点已经在这个图形中出现过,

第二次要判断增加这个点之后的图形是否已经出现过。

如果是可行解,就统计答案,并搜索下一层。

dfs之后的ans[i][j][k]表示表示用i个方块恰好填充j*k的网格的方案数。

我们需要把答案统计起来。

之后的for循环就是做这个用的(具体原理就不细说了,如果不明白的话再开一个数组暴力统计也可以,毕竟n<=10,n^5都能过)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  搜索 uva 图形