【题目链接】
ybt 1955:【11NOIP普及组】瑞士轮
OpenJudge NOI 4.1 4363:瑞士轮
洛谷 P1309 [NOIP2011 普及组] 瑞士轮
【题目考点】
1. 归并排序
2. stl 排序相关函数
- stl:快速排序:
sort(f1,l1,cmp);
f1: stl容器起始迭代器,或数组第1个元素的指针。
l1:stl容器末尾迭代器,或数组最后一个元素后一个位置的指针。
cmp:比较函数
bool cmp(a,b)
参数a与b的类型必须是容器内元素的类型或数组中元素的类型。
返回值为真,则元素a排在前面。返回值为假,则元素b排在前面。 - stl合并有序序列
merge(f1, l1, f2, l2, r, cmp)
f1, l1:容器1的起始与末尾迭代器,或数组1的第一个与最后一个元素后一个位置的指针。
f2, l2:容器2的起始与末尾迭代器,或数组2的第一个与最后一个元素后一个位置的指针。
r:存放结果的容器的起始迭代器,或存放结果的数组的第一个元素的指针
cmp:比较函数
bool cmp(a,b)
参数a与b的类型必须是容器内元素的类型或数组中元素的类型。
返回值为真,则元素a排在前面。返回值为假,则元素b排在前面。
【解题思路】
直接思路:每次用sort函数排序,排名相邻的选手比赛,修改分数后继续排序。该算法需要进行 r r r次排序,每次排序的复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),题目中r能达到50,元素个数为题目中n的二倍,能达到 2 ∗ 1 0 5 2*10^5 2∗105,所以整体复杂度量级为 r ∗ n ∗ l o g 2 n = 50 ∗ 2 ∗ 1 0 5 ∗ l o g 2 ( 2 ∗ 1 0 5 ) = 1 0 7 ( l o g 2 2 + 5 l o g 2 10 ) ≤ 1 0 7 ( l o g 2 2 + 5 l o g 2 16 ) = 2.1 ∗ 1 0 8 r*n*log_2{n}=50*2*10^5*log_2{(2*10^5)}=10^7(log_2{2}+5log_2{10})\le10^7(log_2{2}+5log_2{16})=2.1*10^8 r∗n∗log2n=50∗2∗105∗log2(2∗105)=107(log22+5log210)≤107(log22+5log216)=2.1∗108,不出意外的话一定会超时的。因此这一直接的思路是不行的。
解法1:合并有序序列(归并)
第一次排序后,排名相邻的两选手之间比赛。胜方加1分,负方加0分。考察各选手得分的规律。
记
s
i
s_i
si为第i名选手的分数,
s
i
w
s_{iw}
siw为第i名选手与第i+1名选手比赛中胜者的分数,
s
i
l
s_{il}
sil为败者的分数。
按分数排序后,取任意两组要进行比赛的选手,第
i
i
i名与第
i
+
1
i+1
i+1名比赛,第
j
j
j名与第
j
+
1
j+1
j+1名比赛,且
i
+
1
<
j
i+1<j
i+1<j,那么一定有:
s
i
≥
s
i
+
1
≥
s
j
≥
s
j
+
1
s_i \ge s_{i+1} \ge s_j \ge s_{j+1}
si≥si+1≥sj≥sj+1
现在第
i
i
i名与第
i
+
1
i+1
i+1名进行比赛
- 如果第 i i i名选手获胜,则 s i w = s i + 1 , s i l = s i + 1 s_{iw}=s_i+1,s_{il}=s_{i+1} siw=si+1,sil=si+1。
- 如果第 i + 1 i+1 i+1名选手获胜,则 s i w = s i + 1 + 1 , s i l = s i s_{iw}=s_{i+1}+1,s_{il}=s_{i} siw=si+1+1,sil=si
第 j j j名与第 j + 1 j+1 j+1名进行比赛
- 如果第 j j j名选手获胜,则 s j w = s j + 1 , s j l = s j + 1 s_{jw}=s_j+1,s_{jl}=s_{j+1} sjw=sj+1,sjl=sj+1。
- 如果第 j + 1 j+1 j+1名选手获胜,则 s j w = s j + 1 + 1 , s j l = s j s_{jw}=s_{j+1}+1,s_{jl}=s_{j} sjw=sj+1+1,sjl=sj
可以看出,无论是何种情况,总是有
s
i
w
≥
s
j
w
s_{iw}\ge s_{jw}
siw≥sjw且
s
i
l
≥
s
j
l
s_{il}\ge s_{jl}
sil≥sjl
即,每次比赛后的获胜方的分数为降序序列,失败方的分数也是降序序列。
考虑到分数相等时比较编号,也会有相同的结论。
接下来就是将两个有序序列合并为一个有序序列的过程。
这一方法在归并排序中有使用,合并有序序列的复杂度为
O
(
n
)
O(n)
O(n)
具体写代码时:
写法1:可以将胜方与败方分别放入两个数组,而后合并有序序列。
写法2:数组下标奇数保存胜方,下标偶数保存败方。
使用该算法完成该题,需要循环r次,每次合并有序数组,复杂度为
O
(
r
∗
n
)
O(r*n)
O(r∗n),
r
∗
n
=
50
∗
2
∗
1
0
5
=
1
0
7
r*n=50*2*10^5=10^7
r∗n=50∗2∗105=107,可以通过。
【题解代码】
解法:合并有序序列(归并)
写法1:将胜方与败方分别放入两个数组,而后合并有序序列。
- 手写合并有序序列
#include <bits/stdc++.h>
using namespace std;
#define N 200005
struct Per//选手
{
int i, s, w;//i:编号 s:分数 w:实力
}a[N], w[N], l[N];//a:所有数据 w:胜者组 l:败者组
int n, r, q;
bool cmp(Per a, Per b)
{
if(a.s == b.s)
return a.i < b.i;//分数相等 编号小的在前
else
return a.s > b.s;//分数高的排在前
}
void merge()//合并有序序列w与l
{
int i = 1, j = 1, ai = 1;
while(i <= n && j <= n)
{
if(cmp(w[i], l[j]))//如果w[i]比l[j]优先
a[ai++] = w[i++];
else
a[ai++] = l[j++];
}
while(i <= n)
a[ai++] = w[i++];
while(j <= n)
a[ai++] = l[j++];
}
int main()
{
scanf("%d %d %d", &n, &r, &q);
for(int i = 1; i <= 2*n; ++i)
{
scanf("%d", &a[i].s);
a[i].i = i;//设置编号
}
for(int i = 1; i <= 2*n; ++i)
scanf("%d", &a[i].w);
sort(a+1, a+1+2*n, cmp);
while(r--)
{
for(int i = 1; i <= n; i++)
{
if(a[2*i].w > a[2*i-1].w)//2*i与2*i-1进行一场比赛
{
a[2*i].s++;
w[i] = a[2*i];
l[i] = a[2*i-1];
}
else
{
a[2*i-1].s++;
w[i] = a[2*i-1];
l[i] = a[2*i];
}
}
merge();
}
printf("%d", a[q].i);
return 0;
}
- 使用stl merge函数
#include <bits/stdc++.h>
using namespace std;
#define N 200005
struct Per//选手
{
int i, s, w;//i:编号 s:分数 w:实力
}a[N], w[N], l[N];//a:所有数据 w:胜者组 l:败者组
int n, r, q;
bool cmp(Per a, Per b)
{
if(a.s == b.s)
return a.i < b.i;//分数相等 编号小的在前
else
return a.s > b.s;//分数高的排在前
}
int main()
{
scanf("%d %d %d", &n, &r, &q);
for(int i = 1; i <= 2*n; ++i)
{
scanf("%d", &a[i].s);
a[i].i = i;//设置编号
}
for(int i = 1; i <= 2*n; ++i)
scanf("%d", &a[i].w);
sort(a+1, a+1+2*n, cmp);
while(r--)
{
for(int i = 1; i <= n; i++)
{
if(a[2*i].w > a[2*i-1].w)//2*i与2*i-1进行一场比赛
{
a[2*i].s++;
w[i] = a[2*i];
l[i] = a[2*i-1];
}
else
{
a[2*i-1].s++;
w[i] = a[2*i-1];
l[i] = a[2*i];
}
}
merge(w+1, w+1+n, l+1, l+1+n, a+1, cmp);//stl merge函数 合并有序序列
}
printf("%d", a[q].i);
return 0;
}
写法2:数组下标奇数保存胜方,下标偶数保存败方
#include <bits/stdc++.h>
using namespace std;
#define N 200005
struct Per//选手
{
int i, s, w;//i:编号 s:分数 w:实力
}a[N], t[N];
int n, r, q;
bool cmp(Per a, Per b)
{
if(a.s == b.s)
return a.i < b.i;//分数相等 编号小的在前
else
return a.s > b.s;//分数高的排在前
}
int main()
{
scanf("%d %d %d", &n, &r, &q);
for(int i = 1; i <= 2*n; ++i)
{
scanf("%d", &a[i].s);
a[i].i = i;//设置编号
}
for(int i = 1; i <= 2*n; ++i)
scanf("%d", &a[i].w);
sort(a+1, a+1+2*n, cmp);
while(r--)
{
for(int i = 1; i < 2*n; i += 2)
{
if(a[i].w > a[i+1].w)//i与i+1进行一场比赛
a[i].s++;
else
{
a[i+1].s++;
swap(a[i], a[i+1]);//使胜者的下标为奇数
}
}
int i = 1, j = 2, ti = 1;
while(i <= 2*n-1 && j <= 2*n)//合并下标为奇数的部分与下标为偶数的部分
{
if(cmp(a[i], a[j]))
{
t[ti++] = a[i];
i += 2;
}
else
{
t[ti++] = a[j];
j += 2;
}
}
while(i <= 2*n-1)
{
t[ti++] = a[i];
i += 2;
}
while(j <= 2*n)
{
t[ti++] = a[j];
j += 2;
}
for(int i = 1; i <= 2*n; ++i)
a[i] = t[i];
}
printf("%d", a[q].i);
return 0;
}