解题报告: AT1807
题目链接:AT1807 食塩水
其实是很裸的二分最大化平均值,这里讲一下思路。
很容易发现\(check\)函数很难写,这就是因为找不到用单调性或是什么性质来判断,那我们构造一下不就得了?
假设我们对于一个数\(x\)进行\(check\),
我们设所选的\(k\)个溶液构成的集合为\(S\),那显然只需满足
\[\dfrac{\sum\limits_{i\in S}p_i}{\sum\limits_{i\in S}w_i}\geqslant x
\]
分数很别扭,乘过去,即得:
\[\sum\limits_{i\in S}p_i\geqslant \sum\limits_{i\in S}xw_i
\]
移项,然后把求和合并起来:
\[\sum\limits_{i\in S}(p_i-xw_i)\geqslant 0
\]
维护下每个\(p_i-xw_i\),从大到小排序贪心取前\(k\)大的即可(或后\(k\)小的)。
其他的事:
注意这里的\(p_i\)是浓度,我们要把它处理成溶质的质量;
另外,最后的答案是要\(×100\),因为我们是要用百分数表示答案的。
时间复杂度显然是\(O(n\log n\log eps)\)(这里的\(eps\)指的是精度,\(\log eps\)即二分次数,自己估算一下即可,反正\(100\)准是没问题的。)
\(Code\):
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=1005;
const double EPS=1e-10;
int n,k;
double p[MAXN],w[MAXN];
double r=100.0,l=0.0,mid;
bool check(double x)
{
double rt[MAXN],sum=0;
memset(rt,0,sizeof(rt));//这里的清空是至关重要的,否则数组在函数中会任意赋值
for(int i=1;i<=n;i++) rt[i]=p[i]-x*w[i];
sort(rt+1,rt+n+1);
for(int i=n;i>=n-k+1;i--) sum+=rt[i];//倒序就不用打cmp了
return (sum-0)>EPS;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%lf%lf",&w[i],&p[i]),p[i]*=w[i]/100;
//处理溶质的质量,另外记得用double读入
for(int i=1;i<=100;i++)//二分
{
mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.9lf\n",mid*100);//转化为百分数
return 0;
}