Part2.2排序算法

发布于:2024-05-18 ⋅ 阅读:(73) ⋅ 点赞:(0)

通过排序,我们可以将数据有序化,这让我们对数据的处理方便了很多。

【模板】排序

题目描述

将读入的 N N N 个数从小到大排序后输出。

输入格式

第一行为一个正整数 N N N

第二行包含 N N N 个空格隔开的正整数 a i a_i ai,为你需要进行排序的数。

输出格式

将给定的 N N N 个数从小到大输出,数之间空格隔开,行末换行且无空格。

样例 #1

样例输入 #1

5
4 2 4 5 1

样例输出 #1

1 2 4 4 5

提示

对于 20 % 20\% 20% 的数据,有 1 ≤ N ≤ 1 0 3 1 \leq N \leq 10^3 1N103

对于 100 % 100\% 100% 的数据,有 1 ≤ N ≤ 1 0 5 1 \leq N \leq 10^5 1N105 1 ≤ a i ≤ 1 0 9 1 \le a_i \le 10^9 1ai109

代码实现

#include<iostream>
#include<algorithm>
using namespace std;
#define MAX_N 100000
long long a[MAX_N+5];
int main()
{
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
	scanf("%d",&a[i]);
	sort(a,a+n);
	for(int i=0;i<n;i++)
	{
		if(i)printf(" ");
		printf("%d",a[i]);
	}
	cout<<endl;
	return 0;
}

[NOIP2006 普及组] 明明的随机数

题目描述

明明想在学校中请一些同学一起做一项问卷调查,为了实验的客观性,他先用计算机生成了 N N N 1 1 1 1000 1000 1000 之间的随机整数 ( N ≤ 100 ) (N\leq100) (N100),对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。然后再把这些数从小到大排序,按照排好的顺序去找同学做调查。请你协助明明完成“去重”与“排序”的工作。

输入格式

输入有两行,第 1 1 1 行为 1 1 1 个正整数,表示所生成的随机数的个数 N N N

2 2 2 行有 N N N 个用空格隔开的正整数,为所产生的随机数。

输出格式

输出也是两行,第 1 1 1 行为 1 1 1 个正整数 M M M,表示不相同的随机数的个数。

2 2 2 行为 M M M 个用空格隔开的正整数,为从小到大排好序的不相同的随机数。

样例 #1

样例输入 #1

10
20 40 32 67 40 20 89 300 400 15

样例输出 #1

8
15 20 32 40 67 89 300 400

提示

NOIP 2006 普及组 第一题

代码实现

#include<iostream>
#include<set>
using namespace std;
int main()
{
	set<int>s;
	int n;
	cin>>n;
	int a;
	for(int i=0;i<n;i++)
	{
		scanf("%lld",&a);
		s.insert(a);
	}
	cout<<s.size()<<endl;
	int len=s.size();
	for(int i=0;i<len;i++)
	{
		if(i)printf(" ");
		printf("%lld",*s.begin());
		s.erase(*s.begin());
	}
	return 0;
}

[NOIP2009 普及组] 分数线划定

题目描述

世博会志愿者的选拔工作正在 A 市如火如荼的进行。为了选拔最合适的人才,A 市对所有报名的选手进行了笔试,笔试分数达到面试分数线的选手方可进入面试。面试分数线根据计划录取人数的 150 % 150\% 150% 划定,即如果计划录取 m m m 名志愿者,则面试分数线为排名第 m × 150 % m \times 150\% m×150%(向下取整)名的选手的分数,而最终进入面试的选手为笔试成绩不低于面试分数线的所有选手。

现在就请你编写程序划定面试分数线,并输出所有进入面试的选手的报名号和笔试成绩。

输入格式

第一行,两个整数 n , m ( 5 ≤ n ≤ 5000 , 3 ≤ m ≤ n ) n,m(5 \leq n \leq 5000,3 \leq m \leq n) n,m(5n5000,3mn),中间用一个空格隔开,其中 n n n 表示报名参加笔试的选手总数, m m m 表示计划录取的志愿者人数。输入数据保证 m × 150 % m \times 150\% m×150% 向下取整后小于等于 n n n

第二行到第 n + 1 n+1 n+1 行,每行包括两个整数,中间用一个空格隔开,分别是选手的报名号 k ( 1000 ≤ k ≤ 9999 ) k(1000 \leq k \leq 9999) k(1000k9999)和该选手的笔试成绩 s ( 1 ≤ s ≤ 100 ) s(1 \leq s \leq 100) s(1s100)。数据保证选手的报名号各不相同。

输出格式

第一行,有 2 2 2 个整数,用一个空格隔开,第一个整数表示面试分数线;第二个整数为进入面试的选手的实际人数。

从第二行开始,每行包含 2 2 2 个整数,中间用一个空格隔开,分别表示进入面试的选手的报名号和笔试成绩,按照笔试成绩从高到低输出,如果成绩相同,则按报名号由小到大的顺序输出。

样例 #1

样例输入 #1

6 3 
1000 90 
3239 88 
2390 95 
7231 84 
1005 95 
1001 88

样例输出 #1

88 5 
1005 95 
2390 95 
1000 90 
1001 88 
3239 88

提示

【样例说明】

m × 150 % = 3 × 150 % = 4.5 m \times 150\% = 3 \times150\% = 4.5 m×150%=3×150%=4.5,向下取整后为 4 4 4。保证 4 4 4 个人进入面试的分数线为 88 88 88,但因为 88 88 88 有重分,所以所有成绩大于等于 88 88 88 的选手都可以进入面试,故最终有 5 5 5 个人进入面试。

NOIP 2009 普及组 第二题

代码实现

#include<iostream>
#include<algorithm>
using namespace std;
#define MAX_N 5000
struct Data{
	int id;
	int fen;
};
bool cmp(Data&a,Data&b)
{
	if(a.fen!=b.fen)return a.fen>b.fen;
	return a.id<b.id;
}
Data xuanshou[MAX_N+5];
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=0;i<n;i++)cin>>xuanshou[i].id>>xuanshou[i].fen;
	sort(xuanshou,xuanshou+n,cmp);
	int item_ren=m*1.5,item_fen=xuanshou[item_ren-1].fen;
	while(xuanshou[item_ren-1].fen==xuanshou[item_ren].fen)item_ren++;
	cout<<item_fen<<" "<<item_ren<<endl;
	for(int i=0;i<item_ren;i++)cout<<xuanshou[i].id<<" "<<xuanshou[i].fen<<endl;
	return 0;
}

[NOIP2005 提高组] 谁拿了最多奖学金

题目描述

某校的惯例是在每学期的期末考试之后发放奖学金。发放的奖学金共有五种,获取的条件各自不同:

  1. 院士奖学金,每人 8000 8000 8000 元,期末平均成绩高于 80 80 80 分( > 80 >80 >80),并且在本学期内发表 1 1 1篇或 1 1 1篇以上论文的学生均可获得;
  2. 五四奖学金,每人 4000 4000 4000 元,期末平均成绩高于 85 85 85 分( > 85 >85 >85),并且班级评议成绩高于 80 80 80 分( > 80 >80 >80)的学生均可获得;
  3. 成绩优秀奖,每人 2000 2000 2000 元,期末平均成绩高于 90 90 90 分( > 90 >90 >90)的学生均可获得;
  4. 西部奖学金,每人 1000 1000 1000 元,期末平均成绩高于 85 85 85 分( > 85 >85 >85)的西部省份学生均可获得;
  5. 班级贡献奖,每人 850 850 850 元,班级评议成绩高于 80 80 80 分( > 80 >80 >80)的学生干部均可获得;

只要符合条件就可以得奖,每项奖学金的获奖人数没有限制,每名学生也可以同时获得多项奖学金。例如姚林的期末平均成绩是 87 87 87 分,班级评议成绩 82 82 82 分,同时他还是一位学生干部,那么他可以同时获得五四奖学金和班级贡献奖,奖金总数是 4850 4850 4850 元。

现在给出若干学生的相关数据,请计算哪些同学获得的奖金总数最高(假设总有同学能满足获得奖学金的条件)。

输入格式

第一行是 1 1 1个整数 N N N,表示学生的总数。

接下来的 N N N 行每行是一位学生的数据,从左向右依次是姓名,期末平均成绩,班级评议成绩,是否是学生干部,是否是西部省份学生,以及发表的论文数。姓名是由大小写英文字母组成的长度不超过 20 20 20 的字符串(不含空格);期末平均成绩和班级评议成绩都是 0 0 0 100 100 100 之间的整数(包括 0 0 0 100 100 100);是否是学生干部和是否是西部省份学生分别用 1 1 1 个字符表示, Y \tt Y Y 表示是, N \tt N N 表示不是;发表的论文数是 0 0 0 10 10 10 的整数(包括 0 0 0 10 10 10)。每两个相邻数据项之间用一个空格分隔。

输出格式

3 3 3 行。

  • 1 1 1 行是获得最多奖金的学生的姓名。如果有两位或两位以上的学生获得的奖金最多,输出他们之中在输入文件中出现最早的学生的姓名。
  • 2 2 2 行是这名学生获得的奖金总数。
  • 3 3 3 行是这 N N N 个学生获得的奖学金的总数。

样例 #1

样例输入 #1

4
YaoLin 87 82 Y N 0
ChenRuiyi 88 78 N Y 1
LiXin 92 88 N N 0
ZhangQin 83 87 Y N 1

样例输出 #1

ChenRuiyi
9000
28700

提示

【数据范围】

对于 100 % 100\% 100% 的数据,满足 1 ≤ N ≤ 100 1 \le N \le 100 1N100

【题目来源】

NOIP 2005 提高组第一题

代码实现

#include<iostream>
using namespace std;
#define MAX_N 100
int stu[MAX_N+5];
int main()
{
	int n;
	cin>>n;
	string name,nn;
	int qimo,pingyi,lunwen,max_money=0,all_money=0;
	char ganbu,xibu;
	for(int i=0,money;i<n;i++)
	{
		money=0;
		cin>>name>>qimo>>pingyi>>ganbu>>xibu>>lunwen;
		if(qimo>80&&lunwen>=1)money+=8000;
		if(qimo>85&&pingyi>80)money+=4000;
		if(qimo>90)money+=2000;
		if(qimo>85&&xibu=='Y')money+=1000;
		if(pingyi>80&&ganbu=='Y')money+=850;
		if(money>max_money)
		{
			max_money=money;
			nn=name;
		}
		all_money+=money;
	}
	cout<<nn<<endl;
	cout<<max_money<<endl;
	cout<<all_money<<endl;
	return 0;
}

[NOIP2011 普及组] 瑞士轮

题目背景

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

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

题目描述

2 × N 2 \times N 2×N 名编号为 1 ∼ 2 N 1\sim 2N 12N 的选手共进行R 轮比赛。每轮比赛开始前,以及所有比赛结束后,都会按照总分从高到低对选手进行一次排名。选手的总分为第一轮开始前的初始分数加上已参加过的所有比赛的得分和。总分相同的,约定编号较小的选手排名靠前。

每轮比赛的对阵安排与该轮比赛开始前的排名有关:第 1 1 1 名和第 2 2 2 名、第 3 3 3 名和第 4 4 4名、……、第$2K - 1 名和第 名和第 名和第 2K$名、…… 、第$2N - 1 $名和第 2 N 2N 2N名,各进行一场比赛。每场比赛胜者得$1 $分,负者得 $0 $分。也就是说除了首轮以外,其它轮比赛的安排均不能事先确定,而是要取决于选手在之前比赛中的表现。

现给定每个选手的初始分数及其实力值,试计算在R 轮比赛过后,排名第$ Q$ 的选手编号是多少。我们假设选手的实力值两两不同,且每场比赛中实力值较高的总能获胜。

输入格式

第一行是三个正整数 N , R , Q N,R ,Q N,R,Q,每两个数之间用一个空格隔开,表示有 $2 \times N 名选手、 名选手、 名选手、R$ 轮比赛,以及我们关心的名次 Q Q Q

第二行是 2 × N 2 \times N 2×N 个非负整数 s 1 , s 2 , … , s 2 N s_1, s_2, …, s_{2N} s1,s2,,s2N,每两个数之间用一个空格隔开,其中$ s_i 表示编号为 表示编号为 表示编号为i$ 的选手的初始分数。 第三行是 2 × N 2 \times N 2×N 个正整数 w 1 , w 2 , … , w 2 N w_1 , w_2 , …, w_{2N} w1,w2,,w2N,每两个数之间用一个空格隔开,其中 w i w_i wi 表示编号为 i i i 的选手的实力值。

输出格式

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

样例 #1

样例输入 #1

2 4 2 
7 6 6 7 
10 5 20 15

样例输出 #1

1

提示

【样例解释】

【数据范围】

对于$30% $的数据, 1 ≤ N ≤ 100 1 ≤ N ≤ 100 1N100

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

对于 100 % 100\% 100%的数据, 1 ≤ N ≤ 100 , 000 , 1 ≤ R ≤ 50 , 1 ≤ Q ≤ 2 N , 0 ≤ s 1 , s 2 , … , s 2 N ≤ 1 0 8 , 1 ≤ w 1 , w 2 , … , w 2 N ≤ 1 0 8 1 ≤ N ≤ 100,000,1 ≤ R ≤ 50,1 ≤ Q ≤ 2N,0 ≤ s_1, s_2, …, s_{2N}≤10^8,1 ≤w_1, w_2 , …, w_{2N}≤ 10^8 1N100,000,1R50,1Q2N,0s1,s2,,s2N108,1w1,w2,,w2N108

noip2011普及组第3题。

代码实现

#include<iostream>
#include<algorithm>
using namespace std;
#define MAX_N 100000 
struct Data{
	int id;
	int score;
	int force;
};
int n,r,q;
Data xs[2*MAX_N+5],A[MAX_N+5],B[MAX_N+5];
bool cmp(Data&a,Data&b)
{
	if(a.score==b.score)return a.id<b.id;
	return a.score>b.score;
}
void cpy(Data&a,Data&b)
{
	a.id=b.id;
	a.force=b.force;
	a.score=b.score;
	return ;
}
void msort()
{
	int i=1,j=1,k=1;
	while(i<=n&&j<=n)
	{
		if(A[i].score>B[j].score)cpy(xs[k++],A[i++]);
		else if(A[i].score<B[j].score)cpy(xs[k++],B[j++]);
		else{
			if(A[i].id<B[j].id)cpy(xs[k++],A[i++]);
			else cpy(xs[k++],B[j++]);
		}
	}
	while(i<=n)cpy(xs[k++],A[i++]);
	while(j<=n)cpy(xs[k++],B[j++]);
	return ;
}
int main()
{
	cin>>n>>r>>q;
	for(int i=1;i<=2*n;i++)
	{
		xs[i].id=i;
		cin>>xs[i].score;
	}
	for(int i=1;i<=2*n;i++)cin>>xs[i].force;
	sort(xs+1,xs+1+2*n,cmp);
	while(r--)
	{
		int t=1;
		for(int i=1;i<=2*n-1;i+=2)
		if(xs[i].force<xs[i+1].force)
		{
			cpy(A[t],xs[i+1]);
			A[t].score+=1;
			cpy(B[t],xs[i]);
			t++;
		}
		else {
			cpy(A[t],xs[i]);
			A[t].score+=1;
			cpy(B[t],xs[i+1]);
			t++;
		}
		msort();
	}
	cout<<xs[q].id;
	return 0;
}

逆序对

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 n n n,表示序列中有 n n n个数。

第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109

输出格式

输出序列中逆序对的数目。

样例 #1

样例输入 #1

6
5 4 2 6 3 1

样例输出 #1

11

提示

对于 25 % 25\% 25% 的数据, n ≤ 2500 n \leq 2500 n2500

对于 50 % 50\% 50% 的数据, n ≤ 4 × 1 0 4 n \leq 4 \times 10^4 n4×104

对于所有数据, n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n5×105

请使用较快的输入输出

应该不会 O ( n 2 ) O(n^2) O(n2) 过 50 万吧 by chen_zhe

代码实现

#include<iostream>
using namespace std;
#define MAX_N 500000
int arr[MAX_N+5],temp[MAX_N+5];
long long ans=0;
void msort(int b,int e)
{
	if(b==e)return ;
	int mid=(b+e)/2,i=b,j=mid+1,k=b;
	msort(i,mid),msort(j,e);
	while(i<=mid&&j<=e)
	{
		if(arr[i]<=arr[j])temp[k++]=arr[i++];
		else{
			ans+=(mid-i+1);
			temp[k++]=arr[j++];
		}
	}
	while(i<=mid)temp[k++]=arr[i++];
	while(j<=e)temp[k++]=arr[j++];
	for(int l=b;l<=e;l++)arr[l]=temp[l];
	return ;
}
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%d",&arr[i]);
	msort(1,n);
	cout<<ans;
	return 0;
 }