整数二分模板
当区间[l,r]的更新操作是r = mid;l = mid+1时,计算mid时不需要加1;
int bsearch_1(int l,int r){ while(l < r){ int mid = l+r>>1; if(check(mid))r = mid; else l = mid+1; } return 1; }
当区间[l,r]的更新操作是r = mid-1; l = mid时,计算mid时需要加1;
int bsearch_1(int l,int r){ while(l < r){ int mid = l+r+1>>1; //为了防止死循环,所以计算mid需要加1 if(check(mid))l = mid; else r = mid-1; } return 1; }
注:
- 两种形式的对应mid取法是必要的
- 二分中采用右移运算而不是整数除法/2,右移运算是向下取整,整数除法是向零取整,在二分值域包含负数时后者不能正常工作。
例题 最佳牛围栏
农夫约翰的农场由 NN 块田地组成,每块地里都有一定数量的牛,其数量不会少于1头,也不会超过2000头。
约翰希望用围栏将一部分连续的田地围起来,并使得围起来的区域内每块地包含的牛的数量的平均值达到最大。
围起区域内至少需要包含 F 块地,其中 F 会在输入中给出。
在给定条件下,计算围起区域内每块地包含的牛的数量的平均值可能的最大值是多少。
输入格式
第一行输入整数 N 和 F ,数据间用空格隔开。
接下来 N 行,每行输出一个整数,第i+1行输出的整数代表,第ii片区域内包含的牛的数目。
输出格式
输出一个整数,表示围起区域内每块地包含的牛的数量的平均值可能的最大值乘以1000得到的数值。
数据范围
1≤N≤100000
1≤F≤N
输入样例:
10 6
6
4
2
10
3
8
5
9
4
1
输出样例:
6500
解题思路:
题目意思 在一个连续不单调的序列中选出一部分连续区域
要求:
①区域至少包含F块地
②选出的区域中,每块地的牛的数量的平均值最大
例如上面红色区域,一共四块地,每块地的牛数量分别为3,4,5,6
则每块地的平均值为4.5,假设F为4的话,那么这个平均值明显就是最大的
判断是否存在一个平均值大于等于mid,如果最优解为x,那么mid≤x时,必然可以找到一段其平均值≥mid,否则一定找不到
二分旨在找到一个值一半满足一半不满足,不用满足单调性
由此,可以完成主函数的书写。剩下的,便是对二分后的平均值进行check判断。(后续将这个平均值称为check平均值)
check函数的思路:
①
找到一段不小于F的区域,并且要求这段区间的平均数尽可能大。当找到了一段连续的区间并且区间长度不小于F且平均数大于check平均值,那么大于这个数,且区间也满足条件的平均数,也一定是正确的。
②
根据平均数的性质,对于一段序列来说,每个数减去check平均值,如果结果大于0,那么说明这个数本身大于check平均值,如果结果小于0,那么说明这个数本身小于check平均值。
结合前缀和的思想,将这段序列中每个数与check平均值的差值相加起来,如果≥0,那么说明这段序列是符合check平均值的,如果≤0,那么说明这部分序列的平均值小于check平均值。
这样,就可以找出是否有一段序列能够满足当前的check平均值,从而一步步将check平均值的范围缩小。
③
在②的基础上,进一步进行优化。因为不仅有F大小的区间,还有大于F的区间。
双重for循环的时间复杂度较高,这里可以使用双指针算法,让i = 0、j = F,每次使两个数都同时+1。这样两个指针就始终相差F的距离。然后再使用一个变量minv来存储 i 所遍历到的最小值,minv记录的永远是当前正在判别的序列的最低点,minv是由 i 遍历来确定的,i 和 j 始终相距F并且共同向后移,i 和 j 都是两个不停留的指针,但是minv是停留的(停留在 i 遍历到的最小前缀和处)。j 和 minv 的距离只能是≥F。
由此,用 j 处的前缀和减去minv就可以得到最优解。如果这个最优解≥0,说明符合当前正在check的平均值。
解模板
#include<bits/stdc++.h> using namespace std; const int N = 100010; int n,m; int cows[N]; double sum[N]; bool check(double avg){ for(int i = 1;i <= n;i++){ sum[i] = sum[i - 1] + cows[i] - avg; } double minv = 0; for(int i = 0,j = m;j <= n;j++,i++){ minv = min(minv,sum[i]); if(sum[j] >= minv)return true; } return false; } int main(){ cin >> n >> m; for(int i = 1;i <= n;i++){ cin >> cows[i]; } double l = 0,r = 2000; while(r - l > 1e-5){ double mid = (l + r)/2; if(check(mid)) l = mid; else r = mid; } printf("%d\n",int(r * 1000)); }
例题 特殊排序
有N个元素,编号1.2..N,每一对元素之间的大小关系是确定的,关系不具有传递性。也就是说,元素的大小关系是N个点与N*(N-1)/2条有向边构成的任意有向图。
然而,这是一道交互式试题,这些关系不能一次性得知,你必须通过不超过10000次提问来获取信息,每次提问只能了解某两个元素之间的关系。
现在请你把这N个元素排成一行,使得每个元素都小于右边与它相邻的元素。你可以通过我们预设的bool函数compare来获得两个元素之间的大小关系。例如,编号为a和b的两个元素,如果元素a小于元素b,则compare(a,b)返回true,否则返回false。将N个元素排好序后,把他们的编号以数组的形式输出,如果答案不唯一,则输出任意一个均可。
数据范围
1≤N≤1000
输入样例
[[0, 1, 0], [0, 0, 0], [1, 1, 0]]
输出样例
[3, 1, 2]
注意:不存在两个元素大小相等的情况。
解题思路:
每插入一个数都重复上述的过程,相当于一个插入排序的过程
只有在二分的时候才调用compare函数,二分每个数是logN次,一共N个数,所以compare函数只会调用NlogN次
复杂度为 1*log1+2*log2+…+N*logN
解模板:
class Solution { public: vector<int> specialSort(int N) { vector<int> res; res.push_back(1); for(int i = 2;i <= N;i++){ int l = 0,r = res.size() - 1; while(l < r){ //要注意二分的边界 int mid = l + r >> 1; if(compare(res[mid],i)) l = mid + 1; else r = mid; } res.push_back(i); //把要插入的数先放在最后面 for(int j = res.size() - 2;j > r;j--){ swap(res[j],res[j + 1]); //把插入的数往前挪,直接一一交换,挪到目标位置 } if(compare(i,res[r]))swap(res[r],res[r+1]);//如果当前所有数都比i大,那么将i放到第一个位置 } return res; } };