题解:洛谷 P5461 赦免战俘
【题目来源】
【题目描述】
现有 \(2^n\times2^n(n\le10)\) 名作弊者站成一个正方形方阵等候 kkksc03 的发落。kkksc03 决定赦免一些作弊者。他将正方形矩阵均分为 \(4\) 个更小的正方形矩阵,每个更小的矩阵的边长是原矩阵的一半。其中左上角那一个矩阵的所有作弊者都将得到赦免,剩下 \(3\) 个小矩阵中,每一个矩阵继续分为 \(4\) 个更小的矩阵,然后通过同样的方式赦免作弊者……直到矩阵无法再分下去为止。所有没有被赦免的作弊者都将被处以棕名处罚。
给出 \(n\),请输出每名作弊者的命运,其中 \(0\) 代表被赦免,\(1\) 代表不被赦免。
【输入】
一个整数 \(n\)。
【输出】
\(2^n\times2^n(n\le10)\) 的 \(01\) 矩阵,代表每个人是否被赦免。数字之间有一个空格。
【输入样例】
3
【输出样例】
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
【核心思想】
-
问题分析:给定 \(n\),生成一个 \(2^n \times 2^n\) 的 01 矩阵。规则是:将当前正方形矩阵均分为 4 个等大小的子矩阵,左上角子矩阵全部置为 0(赦免),其余三个子矩阵递归执行同样的操作,直到大小为 1 时停止(此时该位置置为 1?需根据输出理解:最终未被赦免的为 1)。本质是一个递归分形填充问题,类似“谢尔宾斯基三角形”的方形版本。
-
算法选择:
- 递归分治:函数
f(x, y, len)处理左上角为(x, y)、边长为len的正方形。若len == 1,直接返回(或设置该点为 1);否则将左上角len/2 × len/2区域全部置为 0(赦免),然后对右上、左下、右下三个子区域递归调用(继续处理,这些区域最终会保留 1)。 - 实现细节:代码中递归函数
fzdg在len == 1时直接返回,未显式设置 1,但最终未标记为 0 的位置默认输出 1(因为数组初始化为 0,而输出时a[i][j]==1输出 0,否则输出 1)。实际上,递归过程只负责将左上区域置为 1(标记为“填充/赦免”),其余保持 0,最后输出时反转。
- 递归分治:函数
-
关键步骤:
- 读入:读取 \(n\),计算边长
len = 2^n。 - 递归标记:
solve(1, 1, len),将左上角len/2 × len/2区域全部置为 1(表示被赦免,输出为 0),然后递归处理其他三个子区域。 - 输出:按行输出矩阵,若
a[i][j] == 1则输出0,否则输出1。
- 读入:读取 \(n\),计算边长
-
时间/空间复杂度:
- 时间复杂度:\(O(4^n)\),即矩阵大小 \(2^n \times 2^n\),每个位置被访问常数次。
- 空间复杂度:\(O(4^n)\),存储矩阵。
-
递归与分治:
- 分治结构:每次将问题规模缩小为原来的 1/2,处理三个子问题,符合分治思想。
- 递归终止:当边长为 1 时,无法再分,递归结束。
- 反向输出:代码中通过
a数组标记被赦免区域,最后输出时反转,这是常见的技巧。 - 适用场景:这类“递归填充矩阵”问题,是练习分治和递归的好例子,也常见于分形几何的模拟。
【算法标签】
普及 #递归
【代码详解】
#include <bits/stdc++.h> // 包含标准库头文件(万能头文件)
using namespace std; // 使用标准命名空间
int a[1100][1100]; // 定义二维数组:存储分形图案
/**
* 递归生成分形图案
* @param x 当前区域左上角的x坐标
* @param y 当前区域左上角的y坐标
* @param z 当前区域的边长
* @return 无返回值
*/
int fzdg(int x, int y, int z)
{
if (z == 1) // 基本情况:最小单元直接返回
{
return 0;
}
// 填充当前区域左上角的1/4区域
for (int i = x; i < x + z / 2; i++)
{
for (int j = y; j < y + z / 2; j++)
{
a[i][j] = 1; // 标记为填充区域
}
}
// 递归处理其他三个1/4区域
fzdg(x + z / 2, y, z / 2); // 右上区域
fzdg(x, y + z / 2, z / 2); // 左下区域
fzdg(x + z / 2, y + z / 2, z / 2); // 右下区域
}
int main()
{
int n; // 定义变量:分形迭代次数
int fz; // 定义变量:分形图案边长
cin >> n; // 输入迭代次数
fz = pow(2, n); // 计算分形图案边长(2的n次方)
fzdg(1, 1, fz); // 从(1,1)开始生成分形图案
// 输出分形图案(0表示填充,1表示空白)
for (int i = 1; i <= fz; i++)
{
for (int j = 1; j <= fz; j++)
{
if (a[i][j] == 1)
cout << "0 "; // 填充区域输出0
else
cout << "1 "; // 空白区域输出1
}
cout << endl; // 每行结束换行
}
return 0; // 程序正常结束
}
【运行结果】
3
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
浙公网安备 33010602011771号