题解:P4653 [CEOI2017] Sure Bet
前言 & 吐槽
@Seauy 大佬的题解说二分做不了,但其实是可以的。
题面中将灯泡分组的说法具有误导性,实际上不用管这个分组,任意选就行。
思路 & 解析
“最小值最大”是二分答案的经典标志,问题答案具有二段性:在最大的最小值以前,一定有方法使收益达到或超过目标,以后则一定不可能达到。
二分这个最大的最小可能收益 mid。如果有某种方法可以使两类灯泡都能大于等于这个最小可能收益,那么比它小的也都能达到,l=mid;否则如果不存在这样的方法,比它大的都达不到,r=mid。
附一个浮点数二分模板:
const double EPS=1e-6;
double l=0,r=2e8;
while(r-l>EPS)
{
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
问题的关键在于如何找这个方法,即 check 函数怎么写。
贪心的思路,如果要在某一类中选灯泡,选灯泡的代价都一样,那么一定是优先选权值高的灯泡。所以,我们将灯泡按照权值从大到小排序,并预处理出排序后的前缀和。
那么我们就可以枚举一共选择的灯泡数量
然后在每一类灯泡中,选出最少的灯泡使其满足权值和大于等于
最后,如果两类所需要的灯泡数量小于等于
时间复杂度 EPS)。
代码 & 细节
一共有
如果二分查找满足条件的最少灯泡时发现无法使权值和大于等于 break 掉就行了。
代码:
#include<cstdio>
#include<algorithm>
#include<functional>
using namespace std;
const int N=1e5+5;
int n;
double a[N],b[N];
double suma[N],sumb[N];
bool check(double flr)
{
for(int i=1;i<=n<<1;i++) //注意要乘2
{
int mna=lower_bound(suma+1,suma+n+1,flr+i)-suma;
int mnb=lower_bound(sumb+1,sumb+n+1,flr+i)-sumb;
if(mna>n||mnb>n) return false;
//判断越界(找不到可行解),后面再也不可能有解了
if(mna+mnb<=i) return true; //判断条件==和<=均可通过
}
return false;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lf%lf",&a[i],&b[i]);
sort(a+1,a+n+1,greater<double>()); //从大到小排序,下同
sort(b+1,b+n+1,greater<double>());
for(int i=1;i<=n;i++) //预处理前缀和
{
suma[i]=suma[i-1]+a[i];
sumb[i]=sumb[i-1]+b[i];
}
const double EPS=1e-6;
double l=0,r=2e8;
while(r-l>EPS)
{
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.4lf\n",l);
return 0;
}
后记 & 扩展
此方法可以很轻松地扩展为“