【问题描述】
小学期,同学们正在紧锣密鼓地备考九月的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;
}
写了好久啊这篇博客,看完不妨点个赞,感谢支持。