ZOJ 3810 A Volcanic Island

题目大意:给你个n*n个格子的地图,让你划分成形状不同但大小相同的n个区域,形状不同意味着:2块经过平移、旋转和翻折都不能重合。并且要用4种颜色给每个区域染色,相邻的2个区域之间不能同色。

今天做牡丹江网赛重现,由于去年队内教主过了这题,所以我就尝试着做这题,死磕了1个多小时终于想出来了(画了5张草稿纸,然后才想起用excel表格搞)。

首先要声明下  要看结论直接跳过前面的部分看最后两张图,不过对于这种想法题,直接看结果是没什么意义的,所以我会写出我的思考过程,希望能对你有所帮助。

很容易看出是一道构造题目,首先先要解决分区问题,因为染色可以用dfs跑,而且有一个结论是任何一张地图只用四种颜色就能使具有共同边界的国家着上不同的颜色。很久之前看一盘文章就读到过,非常的神奇。所以我们只要考虑如何分区的问题就可以了。

首先,手推一下n很小的情况,显然1是成立的,而2,3,4是不成立的,这里讲述下怎么判断4没有,由于要分成4块每块4个,并且各不相同。每个区域是由4小块构成的,形状不同的只有4种,每种都必须要用的。4种=个区域形状如下。


所以先看1的分布,1只能紧挨在某一边放置,剩下3个很容易发现是没办发合理放置的。说起来有些复杂,不过掌握了这种判断方式后,实际判断n=4,5,6其实都是非常快的。

言归正传,首先我想到的是,由最外层向中心来构造,类似贪吃蛇,最从(1,1)位置向右到(1,n),再向下到(n,n),在向上到(2,1),在向右这样循环下去,然后每n块就换种颜色。

可惜并不对,会出现2个区域重合的现象,甚至连样例中的n=5都没办法推出合理的答案,我又尝试着修补它,例如在出现跟之前重合的部分后,做一些变形什么的。不过都失败了,无法找到一种很合理的有规律的构造方式,之好放弃换下一种。很多时候是不能一次就想到很完美的做法的,我们就要尝试着去改进它,修补它,看能否使之满足要求。一部分人的弊病是想了一种办法,发现有不合理的地方,就开始想下一种构造思路了,这样其实可能跟正确答案擦肩而过,最后看了题解就只好说,这种方法我也想到了,这是没想到接下来还要怎么怎么,其实只是没有很认真的接着去想。而另一部分人的问题是想了一种办法就开始死磕(原因是认为自己好不容易才想到一种方法),不断想要修补它,而不会适当的选择放弃,去找寻下一种思路。至于对某种方法是否可以通过修补而得到正解的把握,就是靠一些题目的积累了。

所以我接着构想,我们构造出的方法必须要有规律性,也就是说,当n为100时你得能顺利的编出来,换句话说,每个部分应该分布很规律或者变化很规律。所以在一次次的失败后想到以下这种构造,下图以n为9为例,此时第一层是8个+1个,下面的每一层和第一层等宽,覆盖着上一层。这样构造下去,右面会空出一列正好再补上一列,如下图。

但这样会有重复,第五行跟第四行就重复了。所以我们就要接着就修补它,可以看出第六行和第三行是重复的,也就是说,从n/2+1行开始,和上面的重复了。

所以上面的部分还是可以再用的。而下面的部分可以用类似的方法构造,从最下面的一行开始向上构造。不过这次第一层要放7个+2个。如下图:


这样我们就能填补剩下的n/2行。而上下两部分中间会空出一部分,长度也是为n的。所以就不会有什么问题了。如下图:


至于正确性的证明,(并不是严谨的证明,只是说着玩)上半部分之间互不冲突(很好证明),下半部分同理,上下2部分之间也不会有重叠。

关键是最后补的黄色的部分,其的形状是固定的,因为上面是n/2行 而最后一行右面也就一定是n/2+1个(加上最右侧的蓝色)

所以从上到下的3部分 分别是1*1   n/2 + 1*1    1*(n / 2 - 1 )的 跟上下两部分都不会有重复,并且一定是一个连续的区域。

至于染色也很好处理了,就想我图中所表示的,最右侧用一种4,补充的用一种3,剩下的1,2交替就可以了。

另外构造的方法也是不唯一的,例如下半部分可以用蛇形去构造。即像贪吃蛇那样蛇形的排列,每到n个格子就换一种颜色,上半部分的前两个区域要用互换一个格子(中心涂色的那两个)来去防止重合。不过代码实现和染色都要稍复杂些。。。

所以你有别的构造方法,也可以自己尝试着去做


最后补充一下,没有人能一下就想到正确的方法,都要走很多的弯路,也都要尝试着去想各种的方法,你没做出来更可能是你半途而废了。做这种题要有耐心和信心,最后附上代码。代码是直接从上往下扫的。


#include<iostream>
#include<cstdio>
#include<cstring>
#include<cctype>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
#define scnaf scanf
#define cahr char
#define bug puts("bugbugbug");
using namespace std;
typedef long long ll;
const int inf=100000000;
const ll mod=1000000007;
int a[105][105];

void print(int n)
{
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
        switch(a[i][j])
        {
            case 1:putchar('R');break;
            case 2:putchar('Y');break;
            case 3:putchar('G');break;
            case 4:putchar('B');break;
        }
        }
        puts("");
    }
}
int go(int n)
{
    memset(a,0,sizeof(a));
    for(int i=1; i<=n; i++)
        a[i][n]=4;
    int color=2;
    for(int k=1; k<=n/2; k++)
    {
        int j;
        if(color==1)color=2;
        else color=1;
        for(j=1;; j++)
        {
            if(a[k][j])break;
            a[k][j]=color;
        }
        for(j--; j<=n-1; j++)
            a[k+1][j]=color;
    }

    for(int i=n/2+2; i<=n; i++)
        a[i][n-1]=3;
    for(int j=n-1; j>=0; j--)
    {
        a[n/2+2][j]=3;
        if(a[n/2+1][j]==0)
        {
            a[n/2+1][j]=3;
            break;
        }
    }
    for(int i=n/2+1; i<=n; i++)
    {
        if(a[i][1])break;
        if(color==1)color=2;
        else color=1;
        int j=1;
        for(; j<=n-2; j++)
        {
            if(a[i][j])break;
            a[i][j]=color;
        }
        j--;
        int k=i;
        for(; k<=n; k++)
        {
            if(a[k][j+1]==0)break;
            a[k][j]=color;
        }
        for(; j<=n-2; j++)
            a[k][j]=color;

    }
}
int main()
{
    int T_T;
    scanf("%d",&T_T);
    while(T_T--)
    {
        int n;
        scanf("%d",&n);
        if(n<=4)
        {
            if(n==1)
                puts("Y");
            else
                puts("No solution!");
            continue;
        }
        go(n);
        print(n);
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值