算法学习01——二分

文章介绍了整数二分查找方法,针对两种情况:一是答案是红色区间的右端点,二是答案是绿色区域的左端点,分别进行区间划分和调整,用于解决给定整数数组中查询特定元素位置的问题。例题展示了如何在C++中实现并应用这种二分查找策略。
摘要由CSDN通过智能技术生成

判断能否使用二分的条件

1、确定一个区间,使得目标值(答案)一定在区间里面

2、找一个性质满足(1)性质具有二段性 (2)答案是二段性的分界点

       整数二分

第一类:ans是红色区间的右端点:将[L,R]分成[L,M-1],[M,R],如果M是红色的,说明ans仍然在[M,R]里面,否则说明ans在[L,M-1]里面

while(L<R)
{
    M=(L+R+1)/2;//记得补上1,如果不补1,可以用两个数的情况来举反例
    if M in 红色区间
    L=M;
    else
    R=M-1;
}

第二类:ans是绿色区域的左端点:将[L,R]分成[L,M],[M+1,R],如果M是绿色的,说明ans在[L,M]之间的,否则说明ans在[M+1,R]里面。

while(L<R)
{
    M=(L+R)/2;//记得补上1,如果不补1,可以用两个数的情况来举反例
    if M in 绿色区间
    R=M;
    else
    L=M+1;
}

总结:L=mid的时候需要加1,R=mid的时候不需要加1 

例题1(很晓畅的整数二分,区间的处理)

数的范围

题目描述
给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。
对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。
如果数组中不存在该元素,则返回“-1 -1”。
输入格式
第一行包含整数n和q,表示数组长度和询问个数。
第二行包含n个整数(均在1~10000范围内),表示完整数组。
接下来q行,每行包含一个整数k,表示一个询问元素。
输出格式
共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回“-1 -1”。
数据范围
1≤n≤100000   1≤q≤10000    1≤k≤10000

样例
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1

下面贴个代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 100010;
int q[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        cin>>q[i];
    }
    
    for(int i=0;i<m;i++)
    {
        int x;
        cin>>x;
        int l=0,r=n-1;
        //首先找的是区间的左端点
        while(l<r)
        {
            int mid=l+r>>1;//因为是r=mid所以不用加1
            if(q[mid]>=x)r=mid;
            else l=mid+1;
        }
        if(q[r]==x)//得判断退出循环后找没找到目标值,因为有可能给的目标值不在数组中
        {
            cout<<r<<" ";
            r=n-1;//寻找区间的右端点,l其实可以不用处理,不过l=0也没什么影响
            while(l<r)
            {
                int mid=(l+r+1)>>1;//因为是l=mid所以需要加1,有点像左加右不变
                if(q[mid]<=x)l=mid;
                else r=mid-1;
            }
            
            cout<<l<<endl;
        }
        
        else cout<<"-1"<<" "<<"-1"<<endl;
    }

    return 0;
}

在CSDN上面看到的一个不需要考虑mid+1、mid-1的二分查找模板

int L=-1,R=n;//这个边界不一样了,y总的是0和n-1
while(L+1!=R)//循环结束的条件也不一样了
{
    int mid=L+R>>1;
    if(check()) L=mid;
    else R=mid;
    //最后根据你所分左右两边区间的结果
    //选取L或者R作为结果
}

为什么L的初始值为-1,R的初始值为N
   首先,如果二分本来就没有结果
比如对于本文例题 1 2 2 3 3 4,,如果你要寻找第一个 >=5 的数,你会发现,整个过程都在执行L=mid,最后得到的结果中,R是等于下标6的,他明显这个时候是越界的,说明我们找不到要寻找的数字,而如果我们一开始将R赋值为n-1,也就是赋值为下标5的时候,他返回的R是5,是没有越界的,被我们当成了答案,但其实这时候我们的二分是没有答案的,就发生了错误;
   其次,L最小值为-1,R最小值只能取到1,因为L+1!=R为循环结束条件,R最大值为N,同理则L的最大值为N-2,则(L+R)/2的取值范围是 [0,N)
   mid的值始终位于0到N的左闭右开区间里面,不会发生越界的错误;

为什么循环结束的条件是while(L+1!=R)?
   之前学过二分的小伙伴可能会发现,之前学的二分,他循环结束的条件是while(L<R)
   而这边给出的循环条件是while(L+1!=R) 其实,就是当L和R相邻的时候,循环就结束,而原本的while(L<R)
是当两区间重合以后,循环才结束,所以之前我们需要判断对mid进行加一或者减一的操作,而且因为区间重合的问题,最后返回的L、R还要再进行判断,而这边的这个二分,因为区间反回的是不重合的两区间,只有L=mid和R=mid这两种情况,最后根据需要返回L或者R;

木材加工

木材加工 - 洛谷

#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, k;
int a[N];
int ans;
bool check(int mid)
{
	int num = 0;
	for (int i = 0; i < n; i++)
	{
		num += int(a[i] / mid);
	}
	if (num >= k)return true;
	else return false;
}

int main()
{
	cin >> n >> k;
	for (int i = 0; i < n; i++)cin >> a[i];

	int l = 0, r = 100000001;
	while (l < r)
	{
		int mid = (l + r+1) / 2;
		if (check(mid))l=mid;
		else r=mid-1;
	}
	cout <<l<< endl;
	return 0;
}

 数列分段 Section II - 洛谷

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n, k;
int a[N];
int ans;

//这个check我没想出来 
bool check(int mid)
{
	int sum=0,num=1;
	for(int i=0;i<n;i++)
	{
		//由于分段的时候是连续的,所以可以直接这样分:满了就置零 
		if(sum+a[i]>mid)sum=0,num++;
		//用很大或者很极端的情况来判断变量是否满足条件
		sum+=a[i];
	}
	return num<=k;
}

int main()
{
	cin>>n>>k;
	int l=0,r=0;
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];
		r+=a[i];//最大最大 
		l=max(l,a[i]);//至少至少 
	}
	
	
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	cout<<l<<endl;
	
	
	return 0;
}

小数二分完全没有上述烦恼

给定一个浮点数 n,求它的三次方根。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
double l,r;
int main()
{
    double n;
    cin>>n;
    l=-10000;
    r=10000;
    while(r-l>1e-8)
    {
        double mid=(l+r)/2;
        if(mid*mid*mid>=n)r=mid;
        else l=mid;
    }
    printf("%.6f",r);
    return 0;
}

洛谷里面的一元三次方程的解

#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
double a, b, c, d;
double f(double x)
{
	return a * x * x * x + b * x * x + c * x + d;
}

int main()
{
	double l, r, mid, x1, x2;
	cin >> a >> b >> c >> d;
	int cnt = 0;
	for (int i = -100; i < 100; i++)
	{
		l = i;
		r = i + 1;
		x1 = f(l);
		x2 = f(r);
		//左端点
		if (x1 == 0) {
			printf("%.2f ", l);
			cnt++;//一个数量标记而已
		}

		if (x1 * x2 < 0)//必有解
		{
			while (r - l > 1e-4)
			{
				mid = (l + r) / 2;
				if (f(mid) * f(r) <= 0)l = mid;
				else r = mid;
			}
			printf("%.2f ", r);
			cnt++;
		}
		if (cnt == 3)break;
	}
	return 0;
}

//做一下暴力的

//int main()
//{
//	cin >> a >> b >> c >> d;
//	for (double i = -100; i < 100; i += 0.001)
//	{
//		double j = i + 0.001;
//		double y1 = a * i * i * i + b * i * i + c * i + d;
//		double y2 = a * j* j * j + b * j * j + c * j + d;
//		if (y1 >= 0 && y2 <= 0 || y1 <= 0 && y2 >= 0)
//		{
//			double x = (i + j) / 2;
//			printf("%.2f ", x);
//		}
//	}
//
//
//	return 0;
//}

…………………………………………

1.24续

今天做的这个题,一眼看上去好像不太像二分…(虽然题解说二分和递推都可以)

730. 机器人跳跃问题 - AcWing题库

“遇到最值问题,思维模型:二分->dfs暴力->DP->贪心”,然后判断题目中潜在的二段性,这个730题的二段性实际上是能量超过一定值(设为E0),超过E0的初始能量值就一定满足,所以这个二分实际上就是找左端点。然后中规中矩写二分就行啦!

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;

int l,r;
int h[N];
int n;
bool check(int e)
{
    for(int i=1;i<=n;i++)
    {
        e=2*e-h[i];
        if(e>=1e5)return true;
        if(e<0)return false;
    }
    return true;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>h[i];
    }
    l=0,r=1e5;
    while(l<r)
    {
        int mid=(l+r)/2;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    printf("%d",r);
    return 0;
}

分巧克力,这个题也就是相当于找右端点,然后难点?如果有的话,应该是巧克力分成的正方形的块数吧……

(h[i]/正方形边长)*(w[i]/正方形边长)就是块数

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;
int n,k;
int h[N];
int w[N];
bool check(int mid)
{
    int sum=0;
    for(int i=1;i<=n;i++)
    {
        int x=h[i]/mid;
        int y=w[i]/mid;
        sum+=x*y;
        if(sum>=k)return true;
    }
    return false;
}

int main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        cin>>h[i]>>w[i];
    }
    int l=0,r=1e5;
    while(l<r)
    {
        int mid=(l+r+1)/2;
        if(check(mid))l=mid;
        else r=mid-1;
    }
    printf("%d",r);
    return 0;
}

https://www.acwing.com/problem/content/1223/四平方和https://www.acwing.com/problem/content/1223/

这个题难点其实是输出字典序!

bool operator< (const Sum& t)const

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=2500010;

struct Sum
{
    int s,c,d;
    bool operator< (const Sum& t)const
    {
        if(s!=t.s)return s<t.s;
        if(c!=t.c)return c<t.c;
        if(d!=t.d)return d<t.d;
    }
}sum[N];


int n,m;

int main()
{
    cin>>n;
    for(int c=0;c*c<=n;c++)
        for(int d=c;c*c+d*d<=n;d++)
            sum[m++]={c*c+d*d,c,d};

    sort(sum,sum+m);
    
    for(int a=0;a*a<=n;a++)
    {
        for(int b=a;b*b+a*a<=n;b++)
        {
            int t=n-a*a-b*b;
            int l=0,r=m-1;
            while(l<r)
            {
                int mid=(l+r)/2;
                if(sum[mid].s>=t)r=mid;
                else l=mid+1;
            }
            if(sum[r].s==t)
            {
               printf("%d %d %d %d",a,b,sum[r].c,sum[r].d);
               return 0;
            }
        }
    }
    

    return 0;
}

STL里面的unordered_map的使用,更加简单

#include <iostream>
#include <cstring>
#include <algorithm>
#include<unordered_map>
using namespace std;
const int N=2500010;
typedef pair<int, int> PII;

unordered_map<int,PII>S;

int n,m;

int main()
{
    cin>>n;
    for(int c=0;c*c<=n;c++)
    {
        for(int d=c;c*c+d*d<=n;d++)
        {
            int t=c*c+d*d;
            if(S.count(t)==0)S[t]={c,d};
        }
    }
    
    for(int a=0;a*a<=n;a++)
    {
        for(int b=a;b*b+a*a<=n;b++)
        {
            int t=n-a*a-b*b;
            if(S.count(t))//查询是否存在
            {
                printf("%d %d %d %d",a,b,S[t].first,S[t].second);
                return 0;
            }
        }
    }
    return 0;
}

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值