L2-047 锦标赛
有
2
k
2^{k}
2k名选手将要参加一场锦标赛。锦标赛共有 k 轮,其中第 i 轮的比赛共有
2
k
−
i
2^{k-i}
2k−i
场,每场比赛恰有两名选手参加并从中产生一名胜者。每场比赛的安排如下:
对于第 1 轮的第 j 场比赛,由第
(
2
j
−
1
)
(2j−1)
(2j−1)名选手对抗第
2
j
2j
2j 名选手。
对于第 i 轮的第 j 场比赛(i>1),由第 (i−1) 轮第 (
2
j
−
1
2j−1
2j−1) 场比赛的胜者对抗第 (i−1) 轮第
2
j
2j
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
。在一场比赛中,若两名选手的能力值不同,则能力值较大的选手一定会打败能力值较小的选手;若两名选手的能力值相同,则两名选手都有可能成为胜者。
令 L(i,j)表示第 i 轮第 j 场比赛 败者 的能力值,令 w 表示整个锦标赛最终胜者的能力值。给定所有满足 1≤i≤k 且 1≤j≤
2
k
−
i
2^{k−i}
2k−i 的 L(i,j) 以及 w,请还原出 a1,a2,⋯,an。
输入格式:
第一行输入一个整数 k(1≤k≤18)表示锦标赛的轮数。
对于接下来 k 行,第 i 行输入 2k−i个整数 L(i,1),L(i,2),⋯,L(i,
2
k
−
i
2^{k−i}
2k−i) (1≤L(i,j) ≤109),其中 L(i,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
首先读题,输入k轮比赛的败者,和最终胜者反过来输出最开始的选手的次序。初看,毫无头绪。仔细思考,题意是让我们将第k轮的败者与这一轮对决出来的胜者安排前后顺序,该轮的胜者全来自于k轮之后决出来的所有败者加上最终的胜者,逐步得到最开始的选手次序。那么应该如何安排前后次序呢?我们可以构造一个满二叉树,每一轮中对决的一对选手视为一对兄弟节点,因为他们俩都是从之前的对决中胜出的,也就是说他们都是以自身为根的树的最大值。故只要是满足这一个条件的顺序即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
//用于标记该子树的最大值
int signal(vector<int> &tree,LL index,vector<int> &maxtree) {
int result=tree[index];
if(index*2+1>=tree.size()){
maxtree[index]=result;
return result;
}
result=max(result,signal(tree,index*2+1,maxtree));
result=max(result,signal(tree,index*2+2,maxtree));
maxtree[index]=result;
return result;
}
int main() {
int k,h;
cin>>k;
h=k+1;//满二叉树的高度
long long len1=1<<h-1;//位运算实现pow(2,h-1),
vector<int> tree(len1*2-1,INT_MIN),maxtree(len1*2-1);//tree为满二叉树数组,maxtree为标记数组
for(int i=0; i<k; i++) {
for(int j=0; j<len1; j+=2) {
cin>>tree[len1-1+j];//最开始将所有的结点放置在左孩子
}
len1/=2;
}
cin>>tree[0];//输入最终胜者
signal(tree,0,maxtree);//标记出每个子树的最大值
if(tree[0]<maxtree[0]){//如果最终胜者的值要小于满二叉树的最大值,则不可能存在
cout<<"No Solution"<<endl;
return 0;
}
tree[2]=tree[0];//先将最终胜者放置在亚军后面
LL len2=1;
for(int i=2; i<=h-1; i++) {
len2*=2;
for(int j=len2-1; j<2*len2-1; j+=2) {//遍历该轮每一对对决
if(tree[j]>=maxtree[j]&&tree[j+1]>=maxtree[j+1]){//如果满足条件
continue;
}
swap(tree[j],tree[j+1]);//不符合条件则交换位置
if(!(tree[j]>=maxtree[j]&&tree[j+1]>=maxtree[j+1])){//依旧不符合条件则说明无解
cout<<"No Solution"<<endl;
return 0;
}
}
for(int j=len2-1; j<2*len2-1; j++){//将该轮对决者全部安插在前一轮的右节点,因为该轮对决者就是前一轮的所有胜者
if(j*2+2<tree.size()){
tree[j*2+2]=tree[j];
}
}
}
for(int i=len2*2-1;i<tree.size();i++){//输出答案
if(i==tree.size()-1){
cout<<tree[i]<<endl;
}else{
cout<<tree[i]<<" ";
}
}
return 0;
}
有的同学可能会想,如果a,b对决,ab和ba都可以,那么我是不是两种情况都要考虑.其实是一样的,如果ab和ba都可以,说明a和b都大于左右子树的最大值,那么,他们俩谁前谁后对于前些轮的对决次序是没有影响的,所有我这里如果ab这种次序可以那么久直接continue了。注意我在这里是使用了maxtree数组来标记最大值,因为如果你每次判断ab或者ba那种符合条件,便就要遍历一次数组这样时间复杂度为 O ( n 3 ) O(n^{3}) O(n3),超时了,所以我选择以空间换时间,一次遍历完数组记录最大值。
小结
看到这里就算结束了。首先题目很长,对于同学们的阅读理解能力很有考究,比赛时能耐下性子来读题还是很不错的,其次还得想到用二叉树数组来存储每轮对决情况,通过抓住题眼(每一对选手都是对应子树的最大值)来安排次序。总的来说,这道题还是非常有难度的。如果还是有不懂的同学,可以在评论区里问出来,我看到的话会回复你的,也希望如果你看懂了的话,帮忙给评论区里不懂的同学解释一下,给别人解释也能帮助自己更加深刻理解这道题。好了,我是白丁,感谢阅读。