[知识点]-[二分]

二分是什么?

二分就是在一个区间[l,r]内找到目标,具体过程就是每次取l和r中间的位置mid,去check检查此时的mid与目标的差别在哪里,然后根据差别去让l或者r移动。

二分是有模版的,如下:

int l = 1,r = n;	//你想找的目标在[l,r]这个区间内
while(l<r)
{
	int mid = (l+r)/2;	//找中间值mid
	if(check(mid)>=x)	r = mid;	//为什么r = mid,而不是r = mid-1,因为check(mid)>=x,此时mid有可能是答案,故r要包含mid。
	if(check(mid)<x)	l = mid+1;	//为什么l = mid+1,而不是l = mid,因为check(mid)<x,此时mid不可能是答案,故l没必要包含mid
}
//注意此时是:l = mid+1,mid = (l+r)/2。不需要搞懂为什么,后期为会具体说
int l = 1,r = n;	//以上同理
while(l<r)
{
	int mid = (l+r+1)/2;	//以上同理
	if(check(mid)>x)	r = mid-1;	//以上同理
	if(check(mid)<=x)	l = mid;	//以上同理
}
//注意此时是:l = mid,mid = (l+r+1)/2。不需要搞懂为什么,后期为会具体说

我们可以发现,上面两个模版,是有区别的。
区别1: l与r赋值为mid-1、mid、mid+1,上面的注释已经解释清楚了。
区别2: mid = (l+r)/2、mid(l+r+1)/2。这个目前不需要知道为什么。
区别2: 可以明显发现,第一个模版的第一个if里面有=号,第二个模版的第二个if里面有=号,这样有什么用?用处就是,当有多个位置符合答案的时候,第一个模版找到的位置会尽量偏左,第二个模版找到的位置会尽量偏右。

例如:
一个长度为7的数组,数组里的数字分别为1 2 2 2 2 2 5。
请你找到2的位置


此时用第一个模版,手动模拟如下:
第一次:l = 1,r = 7,mid = 4。此时va[mid]>=2,故r = mid。
第二次:l = 1,r = 4,mid = 2。此时va[mid]>=2,故r = mid。
第三次:l = 1,r = 2,mid = 1。此时va[mid]<2,故l = mid+1。
第四次:l = 2,r = 2。此时结束,退出循环,答案就在l这个位置。
我们明显发现,明明有5个2,偏偏到找了最左边的2的位置。就是因为当va[mid]==2的时候,我们让r = mid,这样最终位置肯定尽量偏左边。


此时用第二个模版,手动模拟如下:
第一次:l = 1,r = 7,mid = 4。此时va[mid]<=2,故l = mid。
第二次:l = 4,r = 7,mid = 6。此时va[mid]<=2,故l = mid。
第三次:l = 6,r = 7,mid = 7。此时va[mid]>2,故r = mid-1。
第四次:l = 6,r = 6。此时结束,退出循环,答案就在l这个位置。
我们明显发现,明明有5个2,偏偏到找了最右边的2的位置。就是因为当va[mid]==2的时候,我们让l = mid,这样最终位置肯定尽量偏右边。


例题:x的最小最大位置

题意:
给你一个数组,再给你一个x,问你x的最小位置和最大位置。

思考:
明显可以用二分的两个模版直接去做。

代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define PII pair<int,int >
#define int long long 
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);

using namespace std;

const int N = 1e6+10;

int n,x;
int va[N];

int check(int mid)	//mid仅仅是位置,我们需要的是这个位置的值,所以return va[mid]
{
	return va[mid];
}

signed main()
{
	IOS;
	cin>>n>>x;
	for(int i=1;i<=n;i++) cin>>va[i];
	int l = 1,r = n;
	while(l<r)	//找最左边的x
	{
		int mid = (l+r)/2;
		if(check(mid)>=x) r = mid;
		if(check(mid)<x) l = mid+1;
	}
	cout<<l<<" ";
	l = 1,r = n;
	while(l<r)	//找最右边的x
	{
		int mid = (l+r+1)/2;
		if(check(mid)>x) r = mid-1;
		if(check(mid)<=x) l = mid;
	}
	cout<<l;
	return 0;
}

例题:第一个大于x的

题意:
给你一个数组,然后有m次询问,每次问第一个>x的位置,如果没有输出n+1。

思考:
明显是二分的第一个模版,因为求的是第一个位置,也就是最左边的位置。但是有所区别,这里求的是>x的,之前都是>=x的,所以要有所修改。
这里就是if(check(mid)>x) r = mid,这样写就会求出最左边的>x的位置。有同学会疑问,为什么这里没有=号也能求出最左边的?因为我要的是>x的位置,等于x的位置不是我想要的。
而上一题不同,上一题等于x的位置也是我想要的,所以写成if(check(mid)>=x) r = mid。

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define PII pair<int,int >
#define int long long 
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
 
using namespace std;
 
const int N = 1e6+10;
 
int n,m;
int va[N];

int check(int mid)	//mid仅仅是位置,我们需要的是这个位置的值,所以return va[mid]
{
	return va[mid];
}

signed main()
{
    IOS;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>va[i];
    while(m--)
    {
        int x;
        cin>>x;
        int l = 1,r = n;
        while(l<r)
        {
            int mid = (l+r)/2;
            if(check(mid)>x) r = mid;	//这里明明没有等于号,为什么还能求出来最左边>x的位置呢?这是因为我们求的就是>x的,等于x的情况我不需要,所以把=给了下面的if。
            if(check(mid)<=x) l = mid+1;//明明这里有等于号,为什么不是求的最右边的呢?,因为求的是>x的,你此时的check(mid)==x没有任何用,因为这不是答案,答案要的是>x。
        }					
        if(va[l]>x) cout<<l<<"\n";
        else cout<<n+1<<"\n";
    }
    return 0;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值