【问题描述】
在双人对决的竞技性比赛,如万智牌比赛(万智大法好)中,最常见的赛制是淘汰赛和循环赛。前者的特点是比赛场数少,每场都紧张刺激,但偶然性较高。后者的特点是较为公平,偶然性较低,但比赛过程往往十分冗长。
本题中介绍的瑞士轮赛制,因最早使用于 1895 年在瑞士举办的国际象棋比赛而得名。它可以看作是淘汰赛与循环赛的折衷,既保证了比赛的稳定性,又能使赛程不至于过长。
2*N名编号为 1~2N的万智牌手共进行 R轮对局。每轮对局开始前,以及所有比赛结束后,都会按照总分从高到低对选手进行一次排名。 牌手的总分为第一轮开始前的初始分数加上已参加过的所有对局的得分和。总分相同的,约定编号较小的牌手排名靠前。
每轮对局的对阵安排与该轮对局开始前的排名有关:第 1 名和第 2 名、第 3 名和第 4名、……、第 2K – 1 名和第 2K名、…… 、第 2N – 1 名和第2N名,各进行一场对局。每场对局胜者得 1 分,负者得 0 分。也就是说除了首轮以外,其它轮对局的安排均不能事先确定,而是要取决于牌手在之前比赛中的表现。
现给定每个牌手的初始分数及其实力值,试计算在 R 轮比赛过后,排名第 Q 的牌手编号是多少。我们假设牌手的实力值两两不同,且每场比赛中实力值较高的总能获胜。
【输入格式】
输入的第一行是三个正整数 N、R、Q,每两个数之间用一个空格隔开,表示有 2*N名牌手、R 轮对局,以及我们关心的名次 Q。
第二行是 2*N个非负整数 s1, s2, …, s2N,每两个数之间用一个空格隔开,其中 si 表示编号为 i 的选手的初始分数。
【输出格式】
输出只有一行,包含一个整数,即 R 轮比赛结束后,排名第 Q的牌手的编号。
【输入样例】
2 4 2
7 6 6 7
10 5 20 15
【输出样例】
1
【样例解释】
(占位符) | 本轮对阵 | 本轮结束后的得分 | 本轮结束后的得分 | 本轮结束后的得分 | 本轮结束后的得分 |
---|---|---|---|---|---|
选手编号 | \ | 1号 | 2号 | 3号 | 4号 |
初始 | \ | 7 | 6 | 6 | 7 |
第1轮 | ①—④ ②—③ | 7 | 6 | 7 | 8 |
第2轮 | ④—① ③—② | 7 | 6 | 8 | 9 |
第3轮 | ④—③ ①—② | 8 | 6 | 9 | 9 |
第4轮 | ③—④ ①—② | 9 | 6 | 10 | 9 |
【数据范围】
对于 30%的数据,1≤N≤100;
对于 50%的数据,1≤N≤10,000;
对于 100%的数据, 1≤N≤100,000, 1≤R≤50, 1≤Q≤2N, 0≤s1,s2,…,s2N≤100,000,000 ,
1≤w1,w2,…,w2N≤100,000,000 。
【思路梳理】
真是苦了当年的普及组,居然遇到了这样的一道分治题。对于他们来说最好的方法就是快排,每一轮以O(N)的速度统计分数,再以O(n*log(n))的速度快排,5估计连50分都很难得到。
对于提高组来说,学习了分治算法就可以大概有个思路利用归并排序,先根据初始积分高低sort一遍,再以O(n)的时间复杂度确定每一名牌手的新的积分。对于每一场对局都肯定会有一个胜利者和失败者(牌手实力两两不同且实力高的总是能够获胜),那么不妨将所有人分为胜利者和失败者两方。
显然胜利方、失败方的相对排名是固定的(例如a、b之前的积分分别为2和3且都取得了本轮的胜利,那么a、b的积分各自会增加1,也就是说两人的分数的差值仍然不变;对于所有胜利方的牌手,所有人的分数都增加了1,但差值仍然相同,所以互相之间的相对名次不会发生改变),也就是说这个时候一大半的顺序就是已经确定了的,比起时间复杂度不稳定的sort显然应该使用时间复杂度稳定在O(n)的归并排序,确定新的顺序。反复模拟r次就是我们要的答案。
【Cpp代码】
#include<cstdio>
#include<algorithm>
#include<iostream>
#define maxn 100005
using namespace std;
struct data
{
int score,power,id;
}win[2*maxn],lose[2*maxn],ans[2*maxn];
int n,r,Q;
bool cmp(data a,data b)
{
return a.score==b.score? a.id<b.id : a.score>b.score;
}
void solve()
{
int x=0,y=0;
for(int i=1;i<=2*n;i+=2)
{
if(ans[i].power>ans[i+1].power)
{
ans[i].score++;
win[++x]=ans[i];
lose[++y]=ans[i+1];
}
else if(ans[i].power<ans[i+1].power)
{
ans[i+1].score++;
win[++x]=ans[i+1];
lose[++y]=ans[i];
}
}
//非常经典的归并排序,强烈要求读者们熟记
int k=1,i=1,j=1;
while(i<=x && j<=y)
{
if(cmp(win[i],lose[j])) ans[k]=win[i++];
else ans[k]=lose[j++];
k++;
}
while(i<=x) ans[k++]=win[i++];
while(j<=y) ans[k++]=lose[j++];
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d%d%d",&n,&r,&Q);
for(int i=1;i<=2*n;i++) scanf("%d",&ans[i].score),ans[i].id=i;
for(int i=1;i<=2*n;i++) scanf("%d",&ans[i].power);
sort(ans+1,ans+1+2*n,cmp);
for(int i=1;i<=r;i++) solve();
cout<<ans[Q].id;
return 0;
}