洛谷日记 Day 2

昨天晚上太忙碌了,忘写了,今天补发


P1309 瑞士轮

题干:

题目背景

在双人对决的竞技性比赛,如乒乓球、羽毛球、国际象棋中,最常见的赛制是淘汰赛和循环赛。前者的特点是比赛场数少,每场都紧张刺激,但偶然性较高。后者的特点是较为公平,偶然性较低,但比赛过程往往十分冗长。

本题中介绍的瑞士轮赛制,因最早使用于18951895年在瑞士举办的国际象棋比赛而得名。它可以看作是淘汰赛与循环赛的折中,既保证了比赛的稳定性,又能使赛程不至于过长。

题目描述

2 \times N2×N 名编号为 1\sim 2N1∼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 的选手的初始分数。 第三行是2×N 个正整数w1​,w2​,…,w2N​,每两个数之间用一个空格隔开,其中 wi​ 表示编号为i 的选手的实力值。

 

输出格式:

 

一个整数,即RR 轮比赛结束后,排名第Q 的选手的编号。

 

输入输出样例

输入样例#1: 复制

2 4 2 
7 6 6 7 
10 5 20 15 

输出样例#1: 复制

1

说明

【样例解释】

【数据范围】

对于30\%30%的数据, 1≤N≤100;

对于50\%50%的数据, 1≤N≤10,000;

对于100\%100%的数据,1≤R≤50,1≤Q≤2N,0≤s1​,s2​,…,s2N​≤108,1≤w1​,w2​,…,w2N​≤108。

noip2011普及组第3题。



个人思路:

首先对数据建立结构体数组。

struct node {
	int no;//选手编号
	int s;//选手得分
	int w;//选手实力
}Players[MAXN];

然后按轮次计算选手成绩,并使用冒泡法对每一组选手比赛完之后进行排序。

//按分数对结构体进行排序,并在分数相同的情况下按编号排序
inline int cmp_s(node n1,node n2){
	if(n1.s==n2.s){
		return n1.no<n2.no;
	}
	return n1.s>n2.s;
}

对选手按比赛场次次序进行冒泡排序的思想,时间是O(p*n^2)。

inline void check(int cur){
	while(cur>=2 && cmp_s(Players[cur],Players[cur-1])==1)
	{
		swap(Players[cur],Players[cur-1]);
		cur--;
	}
}

inline void get_score(int i){
	if(Players[i-1].w>Players[i].w){
		Players[i-1].s++;
		check(i-1);
	}else if(Players[i-1].w<Players[i].w){
		Players[i].s++;
		check(i);
	}
}

下面是完整代码:

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

const int MAXN=1e6+7; 
struct node {
	int no;
	int s;
	int w;
}Players[MAXN];

inline int cmp_s(node n1,node n2){
	if(n1.s==n2.s){
		return n1.no<n2.no;
	}
	return n1.s>n2.s;
}

inline void check(int cur){
	while(cur>=2 && cmp_s(Players[cur],Players[cur-1])==1)
	{
		swap(Players[cur],Players[cur-1]);
		cur--;
	}
}

inline void get_score(int i){
	if(Players[i-1].w>Players[i].w){
		Players[i-1].s++;
		check(i-1);
	}else if(Players[i-1].w<Players[i].w){
		Players[i].s++;
		check(i);
	}
}


int main(){
   int n,q,r;
   cin>>n>>r>>q;
   n=2*n;
   for(int i=1;i<=n;i++){
   		Players[i].no=i;
   		cin>>Players[i].s;
   }
   for(int i=1;i<=n;i++){
   		cin>>Players[i].w;
   }
   sort(Players+1,Players+1+n,cmp_s);
   n=n/2;
   while(r--){
   		for(int i=1;i<=n;i++){
   			get_score(i*2);
		}
		//out_Players(n);
   }
   cout<<Players[q].no<<endl;
}

最后提交发现严重的超时,仔细阅读之后发现是时间复杂度的问题,在计算选手成绩和排名的整个流程时间复杂度太高,导致一直不能AC。后来去看了一下题解的思路,都说使用归并排序的思路,对当前回合的选手存放在两个数组中,一个数组存放当前回合的赢家,另一个数组存放输家,最后进行归并排序,时间复杂度为O(p*n),明显比我之前的算法效率明显要高出许多于是写了一个归并排序的算法版本,一次就AC,觉得还是满高兴的。

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

const int MAXN=1e6+7; 
struct node {
	int no;
	int s;
	int w;
}Players[MAXN];
node n1[MAXN],n2[MAXN];
inline int cmp_s(node n1,node n2){
	if(n1.s==n2.s){
		return n1.no<n2.no;
	}
	return n1.s>n2.s;
}

inline void merge(int n){
	int k1,k2;
	k1=k2=1;
	int i=1;
	while(k1<=n && k2<=n){
		if(cmp_s(n1[k1],n2[k2])==1){
			Players[i]=n1[k1];
			k1++;
		}else{
			Players[i]=n2[k2];
			k2++;
		}
		i++;
	}
	while(k1<=n){
		Players[i]=n1[k1];
		k1++;	
		i++;
	}
	while(k2<=n){
		Players[i]=n2[k2];
		k2++;
		i++;
	}
}

inline void get_score(int i){
	if(Players[i-1].w>Players[i].w){
		Players[i-1].s++;
		n1[i/2]=Players[i-1];
		n2[i/2]=Players[i];
	}else if(Players[i-1].w<Players[i].w){
		Players[i].s++;
		n1[i/2]=Players[i];
		n2[i/2]=Players[i-1];
	}
}


int main(){
   int n,q,r;
   cin>>n>>r>>q;
   n=2*n;
   for(int i=1;i<=n;i++){
   		Players[i].no=i;
   		cin>>Players[i].s;
   }
   for(int i=1;i<=n;i++){
   		cin>>Players[i].w;
   }
   sort(Players+1,Players+1+n,cmp_s);
   n=n/2;
   while(r--){
   		for(int i=1;i<=n;i++){
   			get_score(i*2);
		}
		merge(n);
   }
   cout<<Players[q].no<<endl;
}

希望以后再接再厉,争取写出自己高质量的博客,这一片就这样水一下吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值