总体思想
当一个点的左右两边有着完全不同的性质的时候即可通过二分方法进行求解,通常求解二分答案,此时常常有着单调性质。
STL
//升序序列中
lower_bound(begin,end,num); //查找第一个大于等于的地址,不存在则返回end
upper_bound(begin,end,num); //查找第一个大于的地址
//降序序列
lower_bound( begin,end,num,greater<type>() );
upper_bound( begin,end,num,greater<type>() );
模版
整数二分
目的:找出刚好的分割点,两边正好满足题目所给限制
一般流程
①通过check()函数判断性质
②根据条件判断哪边一点满足check(),则分割点在另一边,选择方法1或者方法2
③如果方法1则在int mid = l + r >> 1;中加上一个1
//方法1
int l = minn, r = maxx;
while (l < r){
int mid = l + r + 1 >> 1; //防止r - l == 1 && check(l) == true时候死循环
if (check(mid)){
l = mid;
}
else{
r = mid - 1;
}
}
cout << l << endl; //l或r均可
//方法2
int l = minn, r = maxx;
while (l < r){
int mid = l + r >> 1; //无 +1
if (check(mid)){
r = mid;
}
else{
l = mid + 1;
}
}
cout << l << endl; //l或r均可
浮点数二分
double l = minn, r = maxx;
while ((r - l) > eps){ //eps一般比题目给的精度高两位
double mid = (l + r) / 2;
if (check(mid)){
r = mid;
}
else{
l = mid;
}
}
cout << l << endl; //l或r均可
例题
P3853 [TJOI2007]路标设置 (整数二分答案)
题意:
一条路上不同点有路标,空旷指数定义为路标之间的最大距离,如何新增指定数量之下的路标达到尽可能小的空旷指数。
思路:
①首先找关系,我们可以发现增加的路标越多,能达到的最小空旷指数肯定是递减的,对于枚举增加路标数量比较难达成最优结果,使用dp的话至少要二维才有可能实现,因此不予考虑。
②那么我们可以考虑二分答案,对于指定的空旷指数,我们可以在O(n)时间复杂度内找到至少要设置多少路标,可以从前往后推,对于初始的两个相邻的路标之间,如果间距大于空旷指数,那么就要设置路标,最少设置 ceil(差/空旷指数) - 1 个路标,把每个差中间要设置的路标数量相加,即可通过空旷指数推出最小要设置的路标数量,即可二分答案。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x7fffffff;
const int maxn = 1e5 + 5;
int a[maxn];
int L, n, k;
//check函数检查的是对于一个空旷指数dist能否在限制的k个路标内实现
bool check(int dist){
int num = 0; //增加的路标数量
for (int i = 1; i < n; i ++){
int cha = a[i] - a[i - 1];
int t = cha / dist;
if (cha % dist == 0){
num += cha / dist - 1;
}
else{
num += cha / dist;
}
}
if (num <= k){
return true;
}
else{
return false;
}
}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cin >> L >> n >> k;
for (int i = 0; i < n; i ++) cin >> a[i];
int l = 1, r = L;
while (l < r){ //二分模版
int mid = l + r >> 1;
if (check(mid)){
r = mid;
}
else{
l = mid + 1;
}
}
cout << l << endl;
system("pause");
return 0;
}
P3743 kotori的设备 (浮点数二分)
题意:
有n个设备,每个设备有一个初始能量值b[i],和单位时间消耗能量速度a[i],kotori有一个充电宝,充电速度为p,当一个设备能量为0时停止计时,如何让时间持续最长。
思路:
①题目的意思就是找到使用充电设备的决策,有个想法就是首先给最先到0的充电,当充到出现新的最先到0的时在同时间隔充电,一直重复,但这样有一个问题,就是不太好实现,电量是浮点数,同时不同设备电量消耗速度不好计算充电分配时间。
②但是如果知道设备最多能持续的时间,便可通过枚举每台设备算出需要充电多久才能达到该状态,充电设备固定的是充电的电量 = p * 持续时间,那么只要枚举不同设备需要的电量,相加便可判断是否能够通过充电达到该状态,便可二分答案,check判断是否能够实现,最后即可完成求解。
③注意:由于当p = 1.00001,且b[i] = 1.00000时,最后持续的时间可非常大,不只1e5,因此我们可以将最大时间值设定为1e10
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x7fffffff;
const int maxn = 1e5 + 5;
double a[maxn], b[maxn];
int n, p;
bool check(double mid){ //mid:最多使用多久
double timep = 0;
for (int i = 1; i <= n; i++){
if (b[i] / a[i] < mid){
timep += (mid * a[i] - b[i]) / p;
}
}
if (timep <= mid){
return true;
}
else{
return false;
}
}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(NULL);
cin >> n >> p;
double sum = 0;
for (int i = 1; i <= n; i ++){
cin >> a[i] >> b[i];
sum += a[i];
}
if (p >= sum){
cout << -1 << endl;
return 0;
}
double l = 0, r = 1e10;
while ((r - l) > 1e-6){
double mid = (l + r) / 2;
if (check(mid)){
l = mid;
}
else{
r = mid;
}
}
cout << l << endl;
//system("pause");
return 0;
}