不受影响的人

【问题描述】

小学期,同学们正在紧锣密鼓地备考九月的CCF-CSP考试,过程中需要大量的题目练习。有的同学做得又快又好,这能极大地激励其他同学。

假设有同学甲和同学乙,如果甲的做题时间小于等于乙的做题时间,并且甲的题目得分大于等于乙的题目得分,那么我们称乙受甲激励(如甲乙的做题时间和题目得分都相同则甲乙都不能受到激励)。一位同学可以被多位同学激励,也可以激励多位同学。

而这其中就可能有部分大佬是不受激励影响的人。现在我们提供了每位同学做题的时间和得分,请编写一个程序,按照升序输出所有未受激励影响的大佬的id。


【输入形式】

测试数据共 n + 1 行。

第一行输入一个正整数n

接下来n行每行两个正整数题ti、si,表示第i位同学做题的时间与得分,第i位同学的id即为i。

对于70% 的测试数据 1 ≤ n ≤ 1,000

对于100% 的测试数据 1 ≤ n ≤ 1,000,000, 1 ≤ ti ≤ 240, 1 ≤ si ≤ 500


【输出形式】

对于每组测试数据,输出若干行

按照id从小到大依次输出未受激励影响的同学的id,每行仅一个id

【样例输入】

5
2 1
3 2
2 4
4 3
5 2

【样例输出】

3

【样例说明】

同学1被同学3影响

同学2被同学3影响

同学4被同学3影响

同学5被同学2,3,4影响

思路分析:

1.该题本质上还是一个排序问题。只不过要在排序之前先进行一个筛选。筛选条件为不受其他人鼓励:即不存在其他同学得分大于等于他且时间小于等于他。且当两个同学时间和得分都相等时两人都不受彼此鼓励(此类特殊情况应看清楚再做题)。

2.在完成一阶段的筛选后,再对符合条件的人进行按编号从小到大排序。

以上为要求梳理,那么最简单实现操作2的方法就是,从1号开始遍历,符合条件就输出,不符合条件就跳过。即为最简单的方法。

附代码如下

#include<bits/stdc++.h>
using namespace std;
class Student{
	public:
		int number;//编号
		int score;//得分
		int time;//时间 
};
int main()
{
	int n;
	cin>>n;
	Student * a=new Student[n];
	for(int i=0;i<n;i++)
	{
		a[i].number=i+1;
		cin>>a[i].time>>a[i].score;
	 } 
	 for(int i=0;i<n;i++)
	 {
	 	int flag=1;
	 	for(int k=0;k<n;k++)
	 	{
	 		if(k!=i)
	 		{
	 			if(a[k].score>=a[i].score&&a[k].time<=a[i].time)
	 			flag=-1;
	 			break;
			 }
		 }
		 if(flag==1)cout<<a[i].number<<endl;
	 }
}

但是由于用了双重循环,时间复杂度在数据量较大时,最差时间复杂度会来到O(n^2),会导致部分测试点运行时间过长,那么则需要优化算法,降低时间复杂度。那么如何降低呢?

算法优化:

1.第一步想优化算法,最直接的想法当然是将双重循环降低为一重循环。只遍历一遍数据得到结果。但是第一重筛选符合条件的同学需要考虑两个指标 时间和分数。那么关键之处就在于通过排序将双指标降低为单指标问题。

具体来讲,即通过sort函数以及对<的重载先对数据进行一次排序,话不多说,上代码。

class Student {
	public:
		int number;//编号
		int score;//得分
		int time;//时间
		friend bool operator <(Student a,Student b) {
			if(a.time!=b.time)
				return a.time<b.time;
			else
				return a.score>b.score;
		}
};
bool tmp( Student a,Student b) {
	return a<b;
}

通过创建Student 即重载运算符,调用sort函数,先将已有数据按照从小到大进行排序,将双指标降低为单一指标,可能有人会问:怎么就降了呢?我来举个例子

对于下列八个同学的信息,本来直接处理是找不到一个很好的单循环的方法的,但是当我们进行一个sort排序处理后。

timie/score

2       1
3       2
2       4
4       3
5       2
3       6
1       1
4       7

处理后数据:

timie/score

1      1
2      4
2      1
3      6
3      2
4      7
4      3
5      2

我们可以发现一点,从上到下的顺序里,时间是单调不减的,也就是说上方数据的时间一定不小于下方数据的时间,先暂时不考虑时间相等,那么下方的数据(即循环中靠后的同学)时间方面是肯定不可能满足激励需求的,那么靠后同学就不可能激励靠前的同学。那么对于某一特定同学,仅需要考虑在他之前的同学(即时间比他小的同学)是否还存在得分比他高的。如果不存在,那么他就是不受激励的。

经过上述分析,可见这一排序的妙处。但是问题还没有解决完,还有一个关键问题:出现时间相等/时间得分同样相等的该如何处理?

下面给出几段代码供大家思考,哪种是行得通的?

1.

vector <int> v;//向量存满足条件的编号
int max_score=a[0].score;//初始化max_socre
for(int i=1; i<n; i++) {
		if(a[i].score<=max_score) continue;// 等于加不加单一条都不符合 
        v.push_back(a[i].number);
        max_score=max(max_score,a[i].score) ;//更新最大值 
}

2. 同一相比 ,去掉If判断的等于号

for(int i=1; i<n; i++) {
if(a[i].score<=max_score) continue;// 等于加不加单一条都不符合
v.push_back(a[i].number);
max_score=max(max_score,a[i].score) ;//更新最大值
}

这两种乍一看感觉已经将不可能的同学排除在外(continue)语句,但实际上错误之处就在于题干中的神之一笔, 关于等于号的几个判断。(没注意到的再回去好好读读题,思考思考)

1.当你加上=时,当两个同学 均为 2,4  ; 2, 4时,该语句则直接跳过后一同学导致输出缺少。

2.而当你不加=号时,当存在 时间相等(排序中无法剔除,所以之前的分析提到了暂做搁置)但分数不同时,将不符合的也push_back进了向量。例如  : 2,4 ; 2,5

那么经过上述分析可以得知,单一判断是无法实现判定需要的。需要借助另外的空间辅助判断:

两个关键点:

1.maxs[100000001];//开数组存一个时间下的最大得分   开一个数组max[t]即t时间下的最大得分

2.maxi 最大分数所代指的数组编号,用于传导目前最大得分值

最终的判定条件归结为:

1.若该成员的得分小于该时间下的最大得分,continue(剔除同时间,但得分小于其他同学的)

2.若该成员得分小于等于目前的最大得分,且不等于该最大得分对应的时间,continue(剔除小于目前最大得分且不是同一个的)

同时最后注意maxi 以及 max_score的更新。对于筛选出符合条件的同学,对其用向量存储编号,再调用sort函数进行排序,再输出即可。

下面为完整代码:

#include<bits/stdc++.h>
using namespace std;
class Student {
	public:
		int number;//编号
		int score;//得分
		int time;//时间
		friend bool operator <(Student a,Student b) {
			if(a.time!=b.time)
				return a.time<b.time;
			else
				return a.score>b.score;
		}
};
bool tmp( Student a,Student b) {
	return a<b;
}
int maxs[100000001];//开数组存一个时间下的最大得分 
int maxi;
int main() {
	int n;
	vector <int> v;//向量存满足条件的编号
	cin>>n;
	Student * a=new Student[n];
	for(int i=0; i<n; i++) {
		a[i].number=i+1;
		cin>>a[i].time>>a[i].score;
		maxs[a[i].time]=max(maxs[a[i].time],a[i].score);
	} //输入信息完成
	sort(a,a+n,tmp);
	int max_score=a[0].score;//初始化max_socre
		v.push_back(a[0].number);//时间最小肯定符合
		
	for(int i=1; i<n; i++) {
		//if(a[i].score<max_score) continue;// 分数小于目前最大分数
		if(a[i].score<maxs[a[i].time]) continue;
		if(a[i].time!=a[maxi].time&&a[i].score<=a[maxi].score) continue;
		v.push_back(a[i].number);
		if(a[i].score>max_score) maxi=i;
		max_score=max(max_score,a[i].score) ;//更新最大值
		
	}
	sort(v.begin(),v.end());
	for(int i=0; i<v.size(); i++) { //向量用size 没有length
		cout<<v[i]<<endl;
	}
	return 0;

}

写了好久啊这篇博客,看完不妨点个赞,感谢支持。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值