最近做了一点天梯赛的题目,发现老蒋同志选的一道题很有意思
有 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
提示:
本题返回结果若为格式错误均可视为答案错误。
思路:本题真的是想了一下午都没啥思路,然后看的大佬的题解和思路,豁然开朗,这是参考的博主2023天梯赛真题L2-3 锦标赛_l2-3 锦标赛 py-CSDN博客,
然后就是说一下我个人的理解了,以便后来复习的时候找思路。其实这题就是一个满二叉树的锦标赛问题,可以把每两个人的比赛都看成是一个结点,然后里面有胜者和败者的能力。
其实主要需要解决的问题就是怎么才能把上层的每一个结点的两个人合理的按排到与其连接的两个子节点的两个空位(也就是两个胜者)上?
这里的话就有三种可能:
1.就是没有解的情况,也就是我们选出的上层结点胜者要比下层两个子节点的其中能力强的人要弱,或者就是上层节点的败者要比下层两个子节点其中能力弱的要弱,这时候我们就可以直接返回无解。
2.只有一种解的情况,排除1的情况之外,也就是上层节点的败者要弱于下层两个子节点其中能力强的人,这时候就只有一个解。
3.还有就是两种解的情况,也就是上层节点的败者要强于子节点里那个强的,那么就随便安排就可以了,这时候要注意,有可能当前轮有解,但是之后再次安排的时候就没解了,所以我们做两手准备,当我们检测到下层有无解的情况时,我们就回溯,将flag重置,再交换两个胜者的位置,再次进行安排。
这样的话我们就可以实现一个自顶向下的安排的方法,在保证上层安排合理的情况下,进行下层的安排,如果不通的话,那我们就进行返回上一步交换两个胜者的位置,也就是dfs了,所有方法都不通之后我们就可以断定他没有解决方案。
#include "iostream"
#include "algorithm"
using namespace std;
bool flag = true;
struct node{
int win, lose;
};
void solve(node *t,int root, int size){
if(root * 2 > size || !flag) return;
int gt = t[root * 2].lose > t[root * 2 + 1].lose ? root * 2: root * 2 + 1;
int le = t[root * 2].lose < t[root * 2 + 1].lose ? root * 2 : root * 2 + 1;
if(t[gt].lose > t[root].win || t[le].lose > t[root].lose) flag = false;
else if(t[gt].lose > t[root].lose) {
t[gt].win = t[root].win;
t[le].win = t[root].lose;
solve(t, root * 2, size);
solve(t, root * 2 + 1, size);
}
else {
t[gt].win = t[root].lose;
t[le].win = t[root].win;
solve(t, root * 2, size);
solve(t, root * 2 + 1, size);
if(!flag){
flag = true;
swap(t[gt].win, t[le].win);
solve(t, root * 2, size);
solve(t, root * 2 + 1, size);
}
}
}
int main(){
int r;
cin>> r;
int size = (2 << (r - 1) )- 1;
int begin = 1 << (r - 1);
node tree[size + 1];
for(int i = r; i > 0; i --){
int s = 1 << (i - 1);
int t = (2 << (i - 1) )- 1;
while(s --){
cin>>tree[t - s].lose;
// cout<<tree[t - s].lose<<" ";
}
}//这里是为每一层安排败者,稍微计算一下就可以看出来
int win;
cin>>tree[1].win;
// cout<<tree[1].win;
if(tree[1].win < tree[1].lose) flag = false;
else solve(tree, 1, size);
// for(int i = begin; i <= size; i ++){
// cout<<tree[i].lose<<" "<<tree[i].win<<" ";
// }
if(!flag ) cout<<"No Solution"<<endl;
else {
for(int i = begin; i <= size; i ++){
cout<<tree[i].lose<<" "<<tree[i].win;
if(i != size) cout <<" ";
}
}//这里是输出最后一层的结点的所有人,最后一层其实就是第一轮,所以这就是我们需要的结果。
return 0;
}
总结:
总结一下,这道题运用了二叉树的知识,因为首先我们的每一轮的败者的能力都是知道的,其实就可以看成一个满二叉树,每一轮的胜者都可以从父节点的两个值当中选一个出来,这样,结构很清晰,图也很好画,当时其实在做题的时候很自然的就把二叉树画出来了,但是没有想到要把每一轮比赛当成一个结点,只是在想把每个人当成节点,所以就很难把每一轮的胜者和败者联系起来。然后呢这里有运用到二叉树的高度,满二叉树的结点的数目, 满二叉树的底层的结点的数目(牛老师一定会欣慰的,好一次酣畅淋漓的二叉树复习啊),这道题目新颖的就是把二叉树和dfs进行了结合,在满足上层合理的时候再对下层进行安排,如果安排不当,可以有额外的调整方案,非常之妙,做到这道题真的是我三生有幸,大满足了。
ps:小声说一句,我真的强烈建议将这道题放到牛老师的数据结构的锦标赛排序那一章节,做不出来不让走,我想一定会对大家有很大的帮助的(善意的微笑)。