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

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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值