分析:
本题是thu2017年考研机试第一道题。虽然不是太难,但是一旦没找对方法很容易得零分的。
方法一:Hash
完全没思路情况下因为前两个测试点k = 0,也就是要求被选出的人身高都一样,于是考虑每面试一个人就统计同一个身高的人数,达到m时就找到解了。这种思路给了我们两个启示,其一是可以用哈希来解决本题,毕竟最大的身高不会超过十万,其二是当复杂度达不到要求时可以降低标准,力求通过最多的得分点。由于哈希的办法比较容易统计出身高差在k区间范围内的人数,所以我们可以一边读入,一边来c[h[i]]++,h表示身高,c表示处在该身高的人数,同时维护一个长度为k的区间,不停的移动区间,直到该区间内人数不小于m,输出此时的i即是答案。
#include <iostream>
using namespace std;
const int maxn = 100005;
int c[maxn],h[maxn];
int main(){
int n,m,k;
cin>>n>>m>>k;
for(int i = 1;i <= n;i++){
cin>>h[i];
c[h[i]]++;
int sum = 0;
for(int j = 0;j < k;j++) sum += c[j];
for(int j = k;j <= 5000;j++){
sum += c[j];
if(sum >= m){
cout<<i<<endl;
exit(0);
}
sum -= c[j - k];
}
}
cout<<"impossible"<<endl;
return 0;
}
通过上述代码可以发现,我们维护了一个变量sum表示区间长度为k内的人数,为了避免每次移动区间都重新求和,于是在向右移动区间时减去被摒弃的左端点人数再加上新增的右端点人数即可。不难发现,由于每来一个人都要重新判断是否可以找出符合要求的m个人,该算法的时间复杂度为O(nL),L在这里是5000,身高的最大值是十万,n的最大值也是十万,题目的时间限制是1s,显然L设置为十万并不能ac,而设置为5000则可以通过12个测试点,达到及格的分数。
方法二:Hash + 二分
通过上面的分析,我们根据前两个测试点的启示以及数据范围看出可以采用哈希的办法来解决本题。十万的数据范围给我们的启示不仅仅是可以采用哈希实现,还有一点也就是方法一分析过的,平方级的算法绝不可能在一秒内解决本题,如果有nlogn级别的算法,差不多可以在规定时间内解决,由此容易想到使用二分法。
算法复杂度的降低在于消除冗余的计算,可以发现方法一的耗时之处在于每来一个人都要去判断是否可以选出符合要求的m个人。而通过分析不难发现,面试从第一个人到第n个人依次进行,面试到第i个人时,若此时可以满足要求,则不需要继续面试后面的人了;若不满足要求,则前面面试i - 1个人中任意一个人时也均不可能满足要求,换句话说,这些判断都是多余的。由此可以找到问题的单调性。我们至少要面试m个人,至多要面试n个人,于是二分答案,初始l = m,r = n,若mid满足要求,则mid前面也可能有满足要求的解,于是r = mid,若mid不满足要求,必然要继续面试,l = mid + 1,。二分时l和r均是闭区间,当l = r时二分终止,即找到了最优解。
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 100005;
int c[maxn],h[maxn];
int n,m,k;
bool islegal(int x){
memset(c,0,sizeof(c));
for(int i = 1;i <= x;i++) c[h[i]]++;
int sum = 0;
for(int j = 0;j < k;j++) sum += c[j];
for(int j = k;j <= 100000;j++){
sum += c[j];
if(sum >= m){
return true;
}
sum -= c[j - k];
}
return false;
}
int main(){
cin>>n>>m>>k;
for(int i = 1;i <= n;i++) cin>>h[i];
int l = m,r = n;
while(l < r){
int mid = l + r >> 1;
if(islegal(mid)) r = mid;
else l = mid + 1;
}
if(islegal(r)) cout<<r<<endl;
else cout<<"impossible"<<endl;
return 0;
}