分派与月度开销是两种经典的二分题型,前者是让最小值尽可能大,后者是使最大值尽可能小。
在求解时,不妨先搭建出二分求解的框架(while循环,left=初值,right=初值,mid=left+(right-left)/2),再对当前mid进行判断是否合适,这时候需要设计一个函数 isAnswer(mid),该函数内部通常需要一个值cnt来计数(分得派的块数,划分的月数),再将该cnt与问题给定的某个值来比较(人数,周期数)。根据比较结果,这个函数会告诉我们当前mid是否能满足最基本的要求,再采取合适的区间调整策略:
如果能满足要求,则先保留该mid值。若是最小值尽量大的问题,则要到值更大的区间去求解。调整left=mid(或left=mid+x,x为合适的数,视具体情况而定);若是求最大值尽量小的问题,则要到值更小的区间去求解。则调整right=mid(或right=mid-x,x为合适的数,视具体情况而定)。
如果不能满足要求,则和满足要求时采取相反的区间调整策略。
以下,按照我们的策略分别求解题述两个问题。
一、派
1.题目简述
我有N个不同口味、不同大小的派。有F个朋友会来参加我的派对,每个人会拿到一块派(必须一个派的一块,不能由几个派的小块拼成;可以是一整个派)。
所有人拿到的派是同样大小*的(但不需要是同样形状的),我也要给自己留一块,而这一块也要和其他人的同样大小。
请问我们每个人能拿到的最大派是多少?每个派都是一个高为1,半径不等的圆柱体。
输入
第一行包含两个正整数N和F,1 ≤ N, F ≤ 10 000,表示派的数量和朋友的数量。
第二行包含N个1到10000之间的整数,表示每个派的半径。
输出
输出每个人能得到的最大的派的体积,精确到小数点后三位。
样例输入
3 3
4 3 3
样例输出
25.133
2.问题分析
要求将N个派分给F+1个人(千万别忘了自己),求每个人能分到的最大派的面积(体积)。在每个人都有一块派的前提下,尽量使派的面积大,限定条件就是分的派的块数要大于人数,典型的二分求最大值的问题。
假设每个人分的派大小为area:
(1)如果按照该area能分得至少F+1块派,则保留该area值,同时尝试更大的area。令min=area,在更大的区间值里进行计算;
(2)如果按照该area分得派数少于F+1,则说明area选大了,蛋糕总面积不够,需要尝试更小的area。令max=area,在更小的区间值里进行计算。
此外,需要一个函数 isAnswer() 来判断当前area是否能够满足分派要求。
bool isAnswer(double area)
{
int cnt = 0; //蛋糕能分的的总块数
for (int cur = 0; cur < N; cur++)
{
cnt += floor(X[cur] / area);
}
//蛋糕的总块数不少于朋友+自己的数量
return cnt >= F+1;
}
还需注意的是,题目要求保留三位小数,但是二分的精度不能设为10-3或者10-4,否则会导致还能继续增大area值的时候提前退出求解。要注意区分计算精度和保留小数的区别。
3.源代码
// pai.cpp
#include <iostream>
#include <algorithm>
#include <vector>
#include <iomanip>
#include <cmath>
using namespace std;
const double pi = acos