本蒟蒻做瑞士轮,结果发现了点小技巧,在看瑞士轮之前不妨拿来说一下。
一、cmp函数的小技巧
大家都知道,一个数组sort之后各元素(虽然不是真正意义上的各元素)下标会发生改变。有些题目中在进行一些判断时会命令值相等时取下标小的(在输入中出现时间早的),这时候有两种选择,一种是用结构体存各个值,这里讲一下另一种:多开几个数组。
a[]用来存序号,v[]来存值,当然如果有别的条件可以再开其它数组。我们让a数组按v[]从大到小的顺序排,传入两个参数x,y作为两个下标,然后返回v[x]>v[y]即可完成从大到小的排序。这样做的好处是不会打乱v数组,因为进行排序的是a数组,而想访问v也很容易,v[ a[ i ] ]即可
举个例子,某题目(不一定是瑞士轮)要求将n个x从大到小排,输出最大的编号,当x相等时,按y排,y相等时按出现时间早的在前。这时便可如此操作
bool (int x,int y){
if(v[x]==v[y]){
if(s[x]==s[y])
return x<y;
else
return s[x]>s[y];
}
return v[x]>v[y];
}
二、瑞士轮的详解
【题目大意】2*n个选手,每人有初始分与能力值,有r轮比赛,每轮比赛令1 2名对战、3 4名对战、5 6名对战,以此类推。其中每局比赛能力值高者胜,胜者(初始)分值+1,每轮比赛后排名都有可能改变。
【思路介绍】看到此题,最先想到的肯定是for(i~r),每轮sort一遍。但这样会t,我们不妨再好好想想这道题。这道题每轮后排名可能会变,但只可能是邻位之间变化,如果每轮都sort,无疑会造成巨大的时间浪费,而归并排序就非常适合这种小范围的换位。
确认算法之后再想想如何将其实现,该题归并的关键是保证记录名次的数组降序排列。我们想,在每一轮中,比赛开始前是逆序,结束后胜的人分数都+1,还是逆序,败的人分数不变,仍然是逆序。让胜者入win[],败者入lose[],这样每轮win和lose便仍是有序的,所以只需要开两个数组,一个记录胜者,一个记录败者,在每层for(每一轮比赛)结束时进行合并即可。
合并的复杂度为n(2*n)有r轮就是稳定的n(2*n*r)。
【AC】代码及细节解释
1、win[0],lose[0],num[0]统统都是计数器(反正我下标是从1开始记的,闲着也是闲着)这样可以帮你省下多达3*4=12个字节的巨大空间!!!(滑稽)
2、注意一下,选手有n*2个,交了多次才发现
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int const maxn=200111;
int num[maxn],v[maxn],ab[maxn],win[maxn],lose[maxn],n,r,q;
bool cmp(int x,int y){
if(v[x]==v[y])
return x<y;
else
return v[x]>v[y];
}
void merge(){
int i=1,j=1;
num[0]=0;
while(i<=win[0]&&j<=lose[0]){
if(cmp(win[i],lose[j]))
num[++num[0]]=win[i++];
else
num[++num[0]]=lose[j++];
}
while(i<=win[0])
num[++num[0]]=win[i++];
while(j<=lose[0])
num[++num[0]]=lose[j++];
}
int main(){
cin>>n>>r>>q;
n*=2;
for(int i=1;i<=n;i++)
num[i]=i;
for(int i=1;i<=n;i++)
cin>>v[i];
for(int i=1;i<=n;i++)
cin>>ab[i];
sort(num+1,num+1+n,cmp);
for(int i=1;i<=r;i++){
win[0]=lose[0]=0;
for(int j=1;j<=n;j+=2){//两个选手作为一组进行比赛,所以要+=2
if(ab[num[j]]>ab[num[j+1]]){
v[num[j]]++;
win[++win[0]]=num[j];
lose[++lose[0]]=num[j+1];
}
else{
v[num[j+1]]++;
win[++win[0]]=num[j+1];
lose[++lose[0]]=num[j];
}
}
merge();
}
cout<<num[q];
return 0;
}