信息学奥赛一本通 1955:【11NOIP普及组】瑞士轮 | OpenJudge NOI 4.1 4363:瑞士轮 | 洛谷 P1309 [NOIP2011 普及组] 瑞士轮

【题目链接】

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 2105,所以整体复杂度量级为 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 rnlog2n=502105log2(2105)=107(log22+5log210)107(log22+5log216)=2.1108,不出意外的话一定会超时的。因此这一直接的思路是不行的。

解法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} sisi+1sjsj+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+1sil=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+1sil=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+1sjl=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+1sjl=sj

可以看出,无论是何种情况,总是有 s i w ≥ s j w s_{iw}\ge s_{jw} siwsjw s i l ≥ s j l s_{il}\ge s_{jl} silsjl
即,每次比赛后的获胜方的分数为降序序列,失败方的分数也是降序序列。
考虑到分数相等时比较编号,也会有相同的结论。
接下来就是将两个有序序列合并为一个有序序列的过程。
这一方法在归并排序中有使用,合并有序序列的复杂度为 O ( n ) O(n) O(n)
具体写代码时:
写法1:可以将胜方与败方分别放入两个数组,而后合并有序序列。
写法2:数组下标奇数保存胜方,下标偶数保存败方。
使用该算法完成该题,需要循环r次,每次合并有序数组,复杂度为 O ( r ∗ n ) O(r*n) O(rn) r ∗ n = 50 ∗ 2 ∗ 1 0 5 = 1 0 7 r*n=50*2*10^5=10^7 rn=502105=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;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
信息学赛一本 NOIP500 第1部分》是一本参考书籍,其主要目的是为了帮助准备参加NOIP(全国青少年信息学林匹克的省级选拔赛)的学生进行备考。这本书的内容丰富多样,涵盖了计算机科学和编程的各个方面。 在这本书的第1部分,主要介绍了NOIP500比赛的背景和要求。它首先详细解释了NOIP500的含义和意义,以及为什么要参加这样的比赛。它还介绍了NOIP500的考试形式和内容,包括必考的算法、数据结构和编程语言等。此外,它还介绍了评分标准和考试日期等重要信息。 第1部分还包括了一些备考的重要指导和技巧。它解释了如何正确准备和安排备考时间,以及如何理解和解答常见的考试题型。此外,书中还提供了一些实用的练习题和例子,帮助学生加深对知识点的理解和掌握。 这本书的语言简洁明了,结构清晰,非常适合初学者使用。它过大量的例子和习题,帮助学生从基础知识开始逐步提高。此外,书中还附有一些重要的参考资料和学习资源,让学生能够更加全面地了解和学习计算机科学和编程。 总体而言,《信息学赛一本 NOIP500 第1部分》是一本非常有价值的参考书籍,它能够为准备参加NOIP500的学生提供全面和系统的知识,帮助他们在竞赛中取得良好的成绩。对于对信息学感兴趣的学生,这本书也是一本很好的自学教材。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值