二分是我学的最晕的地方之一了,为此特地过来记一个笔记。
这里只是一个笔记而已并不是全面讲解整数二分
二分的原理:
二分的原理实际上就是类似于猜答案,首先知道一个答案的范围,然后每次都用这个范围的中点去试探答案,然后根据结果去不断的缩小范围逼近答案。
整数二分:
模板:
int besearch_1(ll l,ll r)
{
ll mid;
while(l < r)
{
mid = r + l >> 1;
if(check(mid))
r = mid;
else
l = mid + 1;
}
return l;
}
int besearch_1(ll l,ll r)
{
ll mid;
while(l < r)
{
mid = r + l + 1>> 1;
if(check(mid))
l = mid;
else
r = mid - 1;
}
return l;
}
题目一:
题目
意思是给一个排好序的序列(升序), 要求找到一个数输出这个数在这个序列的范围,比如
1
,
2
,
4
,
4
,
4
,
8
,
9
1,2,4,4,4,8,9
1,2,4,4,4,8,9
要找4这个数,那么就输出2和4,要找2就输出1和1,要找7就输出-1和-1(因为没有7这个数)
那么这道题就可以用二分去做
拿查找4这个数来举例(用数组q来储存长度为n的序列)
1.首先要先确定该数所在的范围,4这个数所在的范围是0和6之间,用l和r表示即l = 0, r = 6(整个序列的长度);
2.然后取中间值mid = l + r >> 1;
3.接下来我们对mid进行检查,我们想要确定4这个数就要找它的边界,就拿左边界来说,我们很容易发现4的左边界右边全部大于等于4,右边全部小于4,于是我们可以得到这个结果:
1.当q[mid] >= 4,那么此时mid可能包含左边界,也可能大于左边界,此时我们用mid替换r
2.当q[mid] < 4, 那么此时mid一定不包含边界而且小于边界此时用mid替换掉l + 1
即:
if(q[mid] >= 4)
r = mid;
else
l = mid + 1;
这明显和第一个模板相似,所以套用第一个模板。右边界也同样判断。
题目二:
数列分段
题面中文不再解释,首先要考虑要二分什么,对于这道题,直接二分所求的答案就好了。
1.确定范围
答案最小可能是所有数中最小的一个,这应该是答案最小情况,即左边界
l
l
l的值,又边界的话就是所有值之和了(只分成了一组),明白了这个之后,就是check函数的思路了。
2.check函数
check函数要判断什么呢,这一点我迷惑了很久,其实后来才发现,其实对于一个mid值他只有两种情况而已,要么能把整个序列分成k段而且最大值为mid,要么不能,而对于满足能分的mid值自身也有一个范围,而这个题实际上就是求这个范围的左边界罢了。
#include<bits/stdc++.h>
#define eps 1e-8
#define ll long long
#define INF 0X3f3f3f3f
using namespace std;
const ll maxn = 1e6 + 5;
ll n,num[maxn],k,l,r;
int check(ll mid)
{
ll temp = 0,cnt = 0;
//贪心思想,从左向右按照不超过mid的原则去划分区间,
//得到的cnt就是区间数,如果这个区间数比要划分的k还要大的话
//说明要合并一部分区间达到k个区间,那么此时就会超过mid所以此时的mid一定不满足
//反之可能满足
for(int i = 0; i <= n; i++)
{
if(num[i] + temp <= mid)
temp += num[i];
else
cnt++,temp = num[i];
}
return cnt > k;
}
int main()
{
cin >> n >> k;
num[n] = INF;
for(int i = 0; i < n; i++)
cin >> num[i],r = r + num[i],l = max(l,num[i]);
while(l < r)
{
ll mid = l + r >> 1;
if(check(mid))
l = mid + 1;
else
r = mid;
}
cout << l << endl;
return 0;
}