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

来源:互联网 发布:弗格森事件知乎 编辑:程序博客网 时间:2024/04/29 20:50

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[n][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都能过)。

1 0