【不定期一题】P5461

题目如下:

题目描述

现有 2n×2n(n≤10)2n×2n(n≤10) 名作弊者站成一个正方形方阵等候 kkksc03 的发落。kkksc03 决定赦免一些作弊者。他将正方形矩阵均分为 4 个更小的正方形矩阵,每个更小的矩阵的边长是原矩阵的一半。其中左上角那一个矩阵的所有作弊者都将得到赦免,剩下 3 个小矩阵中,每一个矩阵继续分为 4 个更小的矩阵,然后通过同样的方式赦免作弊者……直到矩阵无法再分下去为止。所有没有被赦免的作弊者都将被处以棕名处罚。

给出 nn,请输出每名作弊者的命运,其中 0 代表被赦免,1 代表不被赦免。

输入格式

一个整数 nn。

输出格式

2n×2n2n×2n 的 01 矩阵,代表每个人是否被赦免。数字之间有一个空格。

输入输出样例

输入 #1

3

输出 #1

0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 1
0 0 0 0 0 1 0 1
0 0 0 0 1 1 1 1
0 0 0 1 0 0 0 1
0 0 1 1 0 0 1 1
0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1

题目分析&解答:

不难看出,这道题的0 1排布很有规律。

我们将首先定义一个数组,这个数组就是我们最后要打印的数组:

bool b[1025][1025] {};    

//编程细节:对数组的部分元素初始化后,未被初始化的元素将会被默认设置为0,这里因为未对任何元素赋值,所以全部的元素都被初始化为0。

由于题目中给出n≤10,故数组元素最多只有1024个,为了能直观展现规律,我们从1开始计数,故为1025。

先暂时不谈中间的计算,由于题目要求我们对于未被赦免的罪犯输出1,这意味着我们要么先对数组的所有值初始化为1,要么在最后打印的时候对数组元素使用!运算符,这里我们选择的是后者。

然后开始分析,观察样例,不难看出0所形成的方块(以下简称0-0块)之间存在明显的关系,且题目所描述的赦免过程,可以用“母体块蔓延”的形式来表述,如图。

 以n = 3时为例,在左上角形成了最初的0-0块,称之为母体块。母体块随后向右、下和右下蔓延,又形成了三个小的0-0块,称为二代母体块。然后每一个二代母体块又向其右、下和右下蔓延,形成三代母体块,然后结束蔓延。

不难看出,母体块蔓延存在一定的规律:

1.每一个母体的边长均为上一代母体的一半

2.每一个母体块至少有一点与上一代母体块相邻

由规律一可以推出:只有当前母体的边长可二分时,它才能蔓延

所以,可以在用程序模拟“母体块蔓延”的过程,然后将被母体块蔓延的部分赋值为true即可。

由此,算法的理论方面已被我们完全分析出来,接下来就是实现部分。

对于母体块的坐标,我们考虑用结构来表示:

struct pos
{
    int x;
    int y;
};

对于蔓延的过程,我们用一个函数来实现:

void change(pos start,int n);

其中注释部分是调试语句,用以确认函数是否正确表达了蔓延过程。

由于此时不需要向调用函数传递信息,故将返回值声明为void。这里对于n进行简单的描述,因为程序需要知道应该在什么时候停止蔓延,所以传递向其传递整型 n 来表示蔓延的进度。

这个函数应该包含三个部分:

1.确定下一代母体的坐标

2.对下一代母体所覆盖的位置赋值

3.判断是否要进行蔓延,若是,则进行下一次蔓延

在蔓延过程完毕后,就是用for循环打印数组了。

最终代码如下:

#include <iostream>
#include <cmath>
using namespace std;
bool b[1025][1025] {};
struct pos
{
    int x;
    int y;
};
void change(pos start,int n);
int main()
{
    int n;
    cin >> n;
    int s = pow(2,n);
    pos start = {1,1};
    for (int i = 1,a = s / 2;i <= a;++i){
        for (int j = 1; j <= a;++j) {
            b[i][j] = true;
            //cout << "(" << j << "," << i << ") has been set true! ";
        }
        //cout << endl;
    }
    if (n > 1)
        change(start,n);
    int k = s - 1;
    for (int i = 1;i <= s;++i)
    {
        for (int j = 1; j <= k;++j)
            cout << !b[i][j] << " ";
        cout << !b[i][s] << endl;
    }
    return 0;
}

void change(pos start,int n)
{
    int w = n - 1;
    int s = n - 2;
    pos start1 {start.x + static_cast<int>(pow(2,w)),start.y};
    pos start2 {start.x,start.y + static_cast<int>(pow(2,w))};
    pos start3 {start1.x,start2.y};
    pos end1 {start1.x + static_cast<int>(pow(2,s)) - 1,start1.y + static_cast<int>(pow(2,s)) - 1};
    pos end2 {start2.x + static_cast<int>(pow(2,s)) - 1,start2.y + static_cast<int>(pow(2,s)) - 1};
    pos end3 {start3.x + static_cast<int>(pow(2,s)) - 1,start3.y + static_cast<int>(pow(2,s)) - 1};
    //cout << "right_up square of " << w << ": (" << start1.x << "," << start1.y << ") to (" << end1.x << "," << end1.y << ")" << endl;
    //cout << "left_down square of " << w << ": (" << start2.x << "," << start2.y << ") to (" << end2.x << "," << end2.y << ")" << endl;
    //cout << "middle square of " << w << ": (" << start3.x << "," << start3.y << ") to (" << end3.x << "," << end3.y << ")" << endl << endl;
    for (int i = start1.y;i<=end1.y;++i) {
        for (int j = start1.x; j <= end1.x; ++j) {
            b[i][j] = true;
            //cout << "(" << j << "," << i << ") has been set true! ";
        }
        //cout << endl;
    }
    for (int i = start2.y;i<=end2.y;++i) {
        for (int j = start2.x; j <= end2.x; ++j){
            b[i][j] = true;
            //cout << "(" << j << "," << i << ") has been set true! ";
        }
        //cout << endl;
    }
    for (int i = start3.y;i<=end3.y;++i){
        for (int j = start3.x;j<=end3.x;++j){
            b[i][j] = true;
            //cout << "(" << j << "," << i << ") has been set true! ";
        }
        //cout << endl;
    }
    if (s)
    {
        change(start1,w);
        change(start2,w);
        change(start3,w);
    }
}

现在对上述程序中二个可能比较令人费解的点进行说明。

首先,下面这段代码乍一看感觉摸不着头脑,但仔细分析便能轻易明白他的作用。

 

int w = n - 1;
    int s = n - 2;
    pos start1 {start.x + static_cast<int>(pow(2,w)),start.y};
    pos start2 {start.x,start.y + static_cast<int>(pow(2,w))};
    pos start3 {start1.x,start2.y};
    pos end1 {start1.x + static_cast<int>(pow(2,s)) - 1,start1.y + static_cast<int>(pow(2,s)) - 1};
    pos end2 {start2.x + static_cast<int>(pow(2,s)) - 1,start2.y + static_cast<int>(pow(2,s)) - 1};
    pos end3 {start3.x + static_cast<int>(pow(2,s)) - 1,start3.y + static_cast<int>(pow(2,s)) - 1};

这段可以不声明新的变量,这里这么做是为了让程序尽可能的清晰。start1,start2,start3分别代指这一次蔓延形成的右、下、右下母体块的左上角的坐标。之所以选择左上角是因为整体的蔓延趋势是自左上至右下的,选择左上作为起始坐标有助于让程序的逻辑清晰。而end1,end2,end3则分别代指这一次蔓延形成的右、下、右下母体块的左上角的坐标。只要知道母体块的起始坐标和当前蔓延的进程,就可以推算出蔓延出的母体块的边长与起始坐标,方法显而易见。

if (s)
    {
        change(start1,w);
        change(start2,w);
        change(start3,w);
    }

对于编程基础牢固的人来说,这段是非常明了的。但是仍会有一些人感到迷惑:s如何作为判别式?对于这种情况,程序会进行隐式类型转换,将所有非零值转化为true,将 0 单独转化为false

结语:

对于这道题,似乎可以通过位运算来判定每一个位置的值是1还是0,但是考虑到时间可能会非常长,所以没有选择它,感兴趣的读者可以自行尝试~

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值