题目描述:
给定 n 门课以及它们的学分和绩点,定义总绩点是所有课的加权平均数,给定一个数 k,你可以删除最多 k 门课,求你的总绩点最大能到多少 1 <=n <=1e5
题目分析:
题目中要求我们最多删k门课,也即要求我们最少选取n-k门课,使得平均绩点gpa最大。这就是一个比较经典的最大化平均值的问题。因为我们要使得平均值最大,而因为平均值受到分子分母同时影响,因此如果我们将成绩按照成绩由大到小或者按照学分由大到小排序进行贪心取的都是不可行的。
因此我们可以考虑二分答案
假设我们定义一个check(x)=可以选择的成绩使得gpa不小于x,那么原问题就转化为求满足check(x)的最大的x。假设我们选取了某门课的集合S,那他们的gpa为:
因此就变成了判断是否存在S满足下面的条件:
将不等式变形后就得到
因此我们对的值进行排序贪心的选择,这就变成了
从大到小排序的前n-k 个 和不小于0
每次的判断复杂度为O(nlogn),故整体时间复杂度为O(nlogn^2)
代码:
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
typedef long long ll;
int s[maxn],c[maxn];
double y[maxn];
int n,k;
bool cmp(double a,double b){
return a>b;
}
bool check(double x){
for(int i=0;i<n;i++){
y[i]=c[i]*s[i]-s[i]*x;
}
sort(y,y+n,cmp);
double sum=0;
for(int i=0;i<k;i++){
sum+=y[i];
}
return sum>=0;
}
int main()
{
scanf("%d%d",&n,&k);
k=n-k;
for(int i=0;i<n;i++){
scanf("%d",&s[i]);
}
for(int i=0;i<n;i++){
scanf("%d",&c[i]);
}
double l=0,r=1e10;
for(int i=0;i<60;i++){//浮点数的比较巧妙的二分方法
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.7f
",r);
}
补充:
在二分判断中,事实上还可以对算法有一个小小的优化,因为我们需要求的是只是前n-k大的数,因此我们大可不需要用sort将所有的数都进行排序,我们只需要求第n-k大的数即可,因此我们可以使用stl中的nth_element()函数将前n-k大的数进行排序即可。这样每次二分判断的时间复杂度即降为O(n),整体时间复杂度将降至O(nlogn)。
代码2:
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
typedef long long ll;
int s[maxn],c[maxn];
double y[maxn];
int n,k;
bool cmp(double a,double b){
return a>b;
}
bool check(double x){
for(int i=0;i<n;i++){
y[i]=c[i]*s[i]-s[i]*x;
}
//sort(y,y+n,cmp);
nth_element(y,y+k,y+n,cmp);//只需要前k大的数就可以了
double sum=0;
for(int i=0;i<k;i++){
sum+=y[i];
}
return sum>=0;
}
int main()
{
scanf("%d%d",&n,&k);
k=n-k;
for(int i=0;i<n;i++){
scanf("%d",&s[i]);
}
for(int i=0;i<n;i++){
scanf("%d",&c[i]);
}
double l=0,r=1e10;
for(int i=0;i<60;i++){//浮点数的独特的二分方法
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.7f
",r);
}