uva1602

uva1602

题意

链接如下:https://vjudge.net/problem/UVA-1602
中文解释:
输入n,w,h(1<=n<=10,1<=w,h<=n).求能放在w*h网格里的不同的n连块的个数(平移,旋转,翻转算一种)
例如:第一排为n=5,w=2,h=4
第2牌为n=8,w=3,h=3
在这里插入图片描述

题解

很直接的思路:打表+STL(set)
重难点是 如何判断重复
因为可以平移,旋转,翻转,所以我们需要给每一个连通块一个标准化(让其的相对起始点都在(0,0))。
利用set实现判重
本题可以先把所有情况都列举出来(打表)。

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <set>

using namespace std;
int n, w, h;
const int N = 10;
struct cell
{
    int r, c;
    cell(int r = 0, int c = 0)
    {
        this->r = r;
        this->c = c;
    }
    bool operator<(const cell rhs)const
    {
        return r < rhs.r || (r == rhs.r && c < rhs.c);
    }
};
typedef set<cell> block;
set<block> s[N + 1];
block normalize(block b)
{
    // 标准化联通块
    int minr = 200, minc = 200;
    for (block::iterator iter = b.begin(); iter != b.end(); iter++)
    {
        minr = min(minr, iter->r);
        minc = min(minc, iter->c);
    }
    block b2;
    for (block::iterator iter = b.begin(); iter != b.end(); iter++)
    {
        b2.insert(cell(iter->r - minr, iter->c - minc));
    }
    return b2;
}
block rotate(block b)
{
    // 顺时针旋转90°
    block ret;
    for (block::iterator iter = b.begin(); iter != b.end(); iter++)
    {
        // 遍历每个元素
        ret.insert(cell(iter->c, (iter->r) * -1));
    }
    return normalize(ret);
}
int ans[N + 1][N + 1][N + 1];
int dr[] = {-1, 1, 0, 0};
int dc[] = {0, 0, -1, 1};
block flip(block b)
{
    // 翻转联通块
    block b2;
    for (block::iterator iter = b.begin(); iter != b.end(); iter++)
    {
        b2.insert(cell(iter->r, -iter->c)); // 沿着x轴翻转
        // 只需要沿着x轴翻转,因为在rotate中会旋转一次
    }
    return normalize(b2);
}
void checkBlock(block b0, cell c)
{
    // 检查当前联通块是否可以加入该新方格
    block b = b0;
    b.insert(c);
    // 标准化联通块
    b = normalize(b);

    int cntBlockSize = b.size();
    for (int i = 0; i < 4; i++)
    {
        if (s[cntBlockSize].count(b))
            return;
        b = rotate(b);
    }
    b = flip(b);
    for (int i = 0; i < 4; i++)
    {
        if (s[cntBlockSize].count(b))
            return;
        b = rotate(b);
    }
    s[cntBlockSize].insert(b);
}
int maxn = 10;
void solve()
{
    // 打表所有情况
    block a;
    a.insert(cell(0, 0));
    s[1].insert(a);
    for (int i = 2; i <= maxn; i++)
    {
        // 遍历每种个数的联通块
        for (set<block>::iterator iter = s[i - 1].begin(); iter != s[i - 1].end(); iter++)
        {
            // 枚举每个连通块
            for (block::iterator citer = (*iter).begin(); citer != (*iter).end(); citer++)
            {
                // 枚举每个联通块的每个单元格
                for (int dir = 0; dir < 4; dir++)
                {
                    // 枚举4个方向
                    cell newc(citer->r + dr[dir], citer->c + dc[dir]);
                    if (iter->count(newc) == 0)
                    {
                        // if判断是否存在该单元格
                        checkBlock(*iter, newc);
                    }
                }
            }
        }
    }
    // 开始打表,枚举每一种情况 
    for (int nn = 1; nn <= maxn; nn++)
    {
        for (int ww = 1; ww <= maxn; ww++)
        {
            for (int hh = 1; hh <= maxn; hh++)
            {
                int cnt = 0;
                for (set<block>::iterator iter = s[nn].begin(); iter != s[nn].end(); iter++)
                {
                    int maxr = 0, maxc = 0;
                    for (block::iterator citer = (*iter).begin(); citer != (*iter).end(); citer++)
                    {
                        maxr = max(maxr, citer->r);
                        maxc = max(maxc, citer->c);
                    }
                    if (min(maxr, maxc) < min(hh, ww) && max(maxr, maxc) < max(hh, ww))
                        cnt++;
                }
                ans[nn][ww][hh] = cnt;
            }
        }
    }
}
int main()
{
    solve(); // vjudge需要400ms
    while (scanf("%d%d%d", &n, &w, &h) != EOF)
    {
        printf("%d\n", ans[n][w][h]);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值