2011NOIP普及组真题 3. 瑞士轮

线上OJ:

一本通:http://ybt.ssoier.cn:8088/problem_show.php?pid=1955

关键词句

“每轮比赛开始前,以及所有比赛结束后,都会按照总分从高到低对选手进行一次排名”。这个句子告诉我们:
1、第一轮比赛之前需要先排一次序,不能直接上来就比;
2、每一轮比赛开始之前(排好序后),队伍是有序的

核心思想: 模拟 + 排序

本题可采用模拟,进行 r 轮后,输出排名第 q 的人即可

题解代码:
解法一、模拟 + 快速排序(60%分数)

排序采用默认的快速排序先试一遍模拟

//#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;

const int N = 200010;
int n, r, q;

struct Node
{
    int s, w, id;  // s为分数,w为实力值,id为选手编号
}a[N];

bool cmp(Node x, Node y)
{
    if( x.s == y.s ) return x.id < y.id;  // 如果分数相同,id 小的排在前面
    else  return x.s > y.s; // 否则。分数高的排在前面
}

int main()
{
    scanf("%d %d %d", &n, &r, &q);
	// 读入并初始化每个选手的分数、id,和实力值
    for(int i = 1; i <= 2 * n; i++)
    {
        scanf("%d", &a[i].s);
        a[i].id = i;
    }
    for(int i = 1; i <= 2 * n; i++)  scanf("%d", &a[i].w);

    sort(a + 1, a + 1 + 2 * n, cmp);  // 按题意,每轮比赛前,先进行一次排序。故第一次比赛前的排序不能遗漏

    while(r--)  // 模拟 r 轮
    {
        for(int i = 1; i <= 2 * n; i += 2)  // 每一次比赛两组人
            a[i].w > a[i+1].w ? a[i].s++ : a[i+1].s++;  // 如果第 i 个人的实力值更大,则第 i 个人的分数++;否则,第 i+1 个人的分数++

        sort(a + 1, a + 1 + 2 * n, cmp); // 一轮结束后重新排序
    }

	printf("%d", a[q].id);

	return 0;
}

以上方法在开了O2的情况下,只能过80%的测试点。

解法二、模拟 + 桶排序(100%)

由于快速排序本身的时间复杂度是 O ( N l o g N ) O(NlogN) O(NlogN),r 轮就是 O ( r ∗ N l o g N ) O(r*NlogN) O(rNlogN),会超时。
仔细研读本题,会发现:
1、每一轮比赛(总共 r 轮)正式开始之前,队伍都是严格有序的
2、每一轮的 每一场 比赛都有 一个胜者一个败者。胜者都是+1分,败者都是+0分
3、这里考虑用 两个桶,一个桶装胜者,一个桶装败者
4、胜者都+1分,相当于没加;败者都+0分,也没加
所以胜者桶里保持着上一轮的排序败者桶里也保持着上一轮的排序
这样一来,每一轮结束后的排序就不再需要O(NlogN)的快速排序,而是直接拿两个桶的第一个数值进行比较,取最大值即可,时间复杂度变为O(N)。

代码如下:

#include <bits/stdc++.h>
using namespace std;

const int N = 200010;
int n, r, q;

struct Node
{
    int s, w, id;  // s为分数,w为实力值,id为选手编号
}a[N], win[N], lose[N];  // 桶排序:每一轮比赛后,赢的人放在赢的桶里,输的人放在输的桶里。这样每个桶内都是默认排好序的,桶内无需再排序

bool cmp(Node x, Node y)
{
    if( x.s == y.s ) return x.id < y.id;  // 如果分数相同,id 小的排在前面
    else  return x.s > y.s; // 否则。分数高的排在前面
}

void tsort()  // 将win和lose两个桶合并为新的a数组
{
    int k1 = 1, k2 = 1, i = 1;
    while( k1 <= n && k2 <= n )
    {
        if( cmp(win[k1], lose[k2]) )   a[i++] = win[k1++];
        else  a[i++] = lose[k2++];
    }

    while( k1 <= n ) a[i++] = win[k1++];
    while( k2 <= n ) a[i++] = lose[k2++];

    return;
}

int main()
{
    scanf("%d %d %d", &n, &r, &q);
	// 读入并初始化每个选手的分数、id,和实力值
    for(int i = 1; i <= 2 * n; i++)
    {
        scanf("%d", &a[i].s);
        a[i].id = i;
    }
    for(int i = 1; i <= 2 * n; i++)  scanf("%d", &a[i].w);

    sort(a + 1, a + 1 + 2 * n, cmp);  // 按题意,每轮比赛前,先进行一次排序。故第一次比赛前的排序不能遗漏

    while(r--)  // 模拟 r 轮
    {
        int k1 = 1, k2 = 1;
        for(int i = 1; i <= 2 * n; i += 2)  // 每一次比赛两组人
        {
            if(a[i].w > a[i+1].w) // 题目已保证实力值无相等的可能性
            {
                a[i].s++;
                win[k1++] = a[i];    // 赢的人放在赢的桶里。赢的人每人都 +1 分,所以赢的桶内无需再排序
                lose[k2++] = a[i+1]; // 输的人放在输的桶里,输的人每人都 +0 分,所以输的桶内也无需再排序
            }
            else
            {
                a[i+1].s++;
                win[k1++] = a[i+1];  // 赢的人放在赢的桶里。赢的人每人都 +1 分,所以赢的桶内无需再排序
                lose[k2++] = a[i];   // 输的人放在输的桶里,输的人每人都 +0 分,所以输的桶内也无需再排序
            }
        }

        tsort(); // 一轮结束后重新排序
    }

	printf("%d", a[q].id);
	return 0;
}
  • 13
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值