原题链接:
PTA | 程序设计类实验辅助教学平台
题面:
有 2k 名选手将要参加一场锦标赛。锦标赛共有 k 轮,其中第 i 轮的比赛共有 2k−i 场,每场比赛恰有两名选手参加并从中产生一名胜者。每场比赛的安排如下:
- 对于第 1 轮的第 j 场比赛,由第 (2j−1) 名选手对抗第 2j 名选手。
- 对于第 i 轮的第 j 场比赛(i>1),由第 (i−1) 轮第 (2j−1) 场比赛的胜者对抗第 (i−1) 轮第 2j 场比赛的胜者。
第 k 轮唯一一场比赛的胜者就是整个锦标赛的最终胜者。
举个例子,假如共有 8 名选手参加锦标赛,则比赛的安排如下:
- 第 1 轮共 4 场比赛:选手 1 vs 选手 2,选手 3 vs 选手 4,选手 5 vs 选手 6,选手 7 vs 选手 8。
- 第 2 轮共 2 场比赛:第 1 轮第 1 场的胜者 vs 第 1 轮第 2 场的胜者,第 1 轮第 3 场的胜者 vs 第 1 轮第 4 场的胜者。
- 第 3 轮共 1 场比赛:第 2 轮第 1 场的胜者 vs 第 2 轮第 2 场的胜者。
已知每一名选手都有一个能力值,其中第 i 名选手的能力值为 ai。在一场比赛中,若两名选手的能力值不同,则能力值较大的选手一定会打败能力值较小的选手;若两名选手的能力值相同,则两名选手都有可能成为胜者。
令 li,j 表示第 i 轮第 j 场比赛 败者 的能力值,令 w 表示整个锦标赛最终胜者的能力值。给定所有满足 1≤i≤k 且 1≤j≤2k−i 的 li,j 以及 w,请还原出 a1,a2,⋯,an。
输入格式:
第一行输入一个整数 k(1≤k≤18)表示锦标赛的轮数。
对于接下来 k 行,第 i 行输入 2k−i 个整数 li,1,li,2,⋯,li,2k−i(1≤li,j≤109),其中 li,j 表示第 i 轮第 j 场比赛 败者 的能力值。
接下来一行输入一个整数 w(1≤w≤109)表示锦标赛最终胜者的能力值。输出格式:
输出一行 n 个由单个空格分隔的整数 a1,a2,⋯,an,其中 ai 表示第 i 名选手的能力值。如果有多种合法答案,请输出任意一种。如果无法还原出能够满足输入数据的答案,输出一行
No Solution
。
请勿在行末输出多余空格。输入样例1:
3 4 5 8 5 7 6 8 9
输出样例1:
7 4 8 5 9 8 6 5
输入样例2:
2 5 8 3 9
输出样例2:
No Solution
提示:
本题返回结果若为格式错误均可视为答案错误。
解题思路:
本质上是一个灵活使用完全二叉树的题目。
每个节点存储一场比赛的胜负者能力值:
struct node {int win;int lose;
} tree[1 << 20];
因为是完全二叉树,所以我们不必使用动态存储方式,不必使用指针来实现,可以用数组实现,假设当前节点为tree[root],那么它的左子节点为tree[root * 2],右子节点为tree[root * 2 + 1]。
根节点存储决赛的胜负者,第二层节点存储第k-1轮的胜负者,第三层节点存储第k-2轮的胜负者,……最后一层节点存储第一轮的胜负者。
接收题目的输入之后,我们得到了每个节点的lose值,并且只有决赛也就是根节点的win和lose值是全部都已知的。
此时我们先进行一个特判,根节点如果lose值大于win值,则代表题目给出的数据是无解的。
然后要做的事情就是填写除了根节点以外的win值,因为会存在试错回溯的过程,所以很直观想到的解法就是递归。
递归函数为dfs(int root, int size);root为当前节点编号,size为二叉树的节点数量。
我们先设当前节点的两个子节点中,更强的败者所在的节点编号为bLoser,更弱的败者所在的节点编号为sLoser。
递归过程中我们用当前节点的win和lose去填充子节点的win,会出现以下几种情况:
(1)无解:如果当前节点的胜者弱于bLoser的败者,或者当前的败者弱于sLoser的败者,则证明这个解是错误的,需要回溯。
(2)有解:
一种可能解:如果bLoser的败者强于当前节点的败者,那么当前节点的败者只能是sLoser的胜者,此时只有一种解,将当前节点的胜者填入bLoser,将当前节点的败者填入sLoser。
两种可能解:此时两种填法都有可能成为正解,所以我们需要将两种解法都尝试一遍,如果第一种尝试是错误的,那么我们在递归返回之后需要尝试第二种,因为这里我们需要在第一种尝试完毕递归返回之后判断是否是错误解需要尝试另一种,所以我们需要开一个bool全局变量errFlag来记录。
光看上边的解释可能很复杂,很懵圈,具体实现见代码。
代码(CPP):
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e3 + 10;
const int INF = 0x3fffffff;
const int mod = 1000000007;
struct node {int win;int lose;
} tree[1 << 20];
bool errFlag = false;void dfs(int root, int size) {// 递归边界:已经到叶子节点或者此时已经是错误解状态,可以提前返回if (root * 2 > size || errFlag) {return;}// 得到两个子节点的更强败者以及更弱败者int bLoser = (tree[root * 2].lose > tree[root * 2 + 1].lose ? root * 2 : root * 2 + 1);int sLoser = (tree[root * 2].lose < tree[root * 2 + 1].lose ? root * 2 : root * 2 + 1);// 如果当前的胜者弱于子节点更强的败者,或者当前的败者弱于子节点更弱的败者,则证明这个解是错误的if (tree[root].win < tree[bLoser].lose || tree[root].lose < tree[sLoser].lose) {errFlag = true;return;}// 判断if (tree[bLoser].lose > tree[root].lose) {// 如果子节点更强的败者强于当前节点的败者,那么当前节点的败者只能是另一个子节点的胜者,此时只有一种解tree[sLoser].win = tree[root].lose; // 当前的败者一定是从更弱的那边晋级上来的tree[bLoser].win = tree[root].win; // 当前的胜者一定是从更强的那边晋级上来的dfs(root * 2, size);dfs(root * 2 + 1, size);} else {// 此时两种解都有可能,都要递归尝试一遍tree[sLoser].win = tree[root].lose;tree[bLoser].win = tree[root].win;dfs(root * 2, size);dfs(root * 2 + 1, size);if (errFlag) {errFlag = false;swap(tree[sLoser].win, tree[bLoser].win);dfs(root * 2, size);dfs(root * 2 + 1, size);}}
}void solve() {int k;cin >> k;int size = (2 << (k - 1)) - 1;// 输入每一轮每场比赛的败者能力值for (int i = k; i > 0; i--) {int n = (1 << (i - 1));int s = (2 << (i - 1)) - n;while (n--) {cin >> tree[s].lose;s++;}}// 输入最后的冠军能力值,如果冠军能力值低于亚军则证明这是无解的cin >> tree[1].win;if (tree[1].lose > tree[1].win) {errFlag = true;} else {dfs(1, size); // 递归填写每一轮每场的胜者能力值}// 输出结果if (errFlag) {cout << "No Solution\n";} else {int n = (1 << (k - 1));int s = (2 << (k - 1)) - n;while (n--) {cout << tree[s].win << " " << tree[s].lose;s++;if (n)cout << " ";}}
}int main() {// freopen("in.txt", "r", stdin);ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cout << fixed;cout.precision(18);solve();return 0;
}