二分总结

二分是一种高校的查找数据方式,每次查找的时间复杂度为\(O(logn)\)
不过前提条件是所查找的数据具有单调性

二分

二分具有两种类型1.二分查找 2.二分答案

二分的特征,也可以理解为具有二段性
一端满足我们的需求,另一端不满足我们的需求,一般题目会要求我们求这个临界点

二分查找

顾名思义,通过二分查找所找需数据

基础题型

P2249 【深基13.例1】查找

inline int find(int x){
	int l=1,r=n;
	while(l<=r){
		int mid=(l+r)>>1;
		if(a[mid]>=x) r=mid-1;//第一次出现的位置,如果有多个,查到相同的就可以继续缩小有边界
		else l=mid+1;
	}
	if(a[l]==x) return l;
	return -1;
		

二分答案

二分答案的使用建立在答案具有单调性的性质之上

  • 什么是答案有单调性?
    从牛客小白月赛104之后又有新的认识,我们所寻找的答案,从某个合法答案开始,增加了仍然可以合法,但减少了就不合法,而二分答案一般是寻找最合适的那个答案
    我的解释可能不太懂,但是二分答案题做多了,自然就能理解了
  • 二分答案题的一种特征:
    我们通过纯粹的模拟或者暴力,难以得到一个好的程序得出答案,或者说找到答案的过程比较难以设计
    但是如果我们预先知道答案,那么检验答案的正确性的程序会更加容易

STL的使用

  1. lower_bound

定义

  • lower_bound 返回指向集合中第一个不小于目标值的元素的迭代器。

语法

  • 对于集合 setmultiset
    set<int>::iterator it = s.lower_bound(value);
    
  • 对于数组或容器:
    auto it = lower_bound(arr, arr + n, value);
    

返回值

  • 返回一个迭代器,指向第一个不小于 value 的元素。
  • 如果所有元素都小于 value,返回集合的末尾迭代器 end()

用途

  • 用于查找不小于目标值的最小元素。
  • 常用于插入操作,确定插入位置以保持有序。

示例

点击查看代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    set<int> s = {1, 2, 3, 4, 5};
    auto it = s.lower_bound(3); // 返回指向 3 的迭代器
    cout << *it << endl; // 输出 3

    it = s.lower_bound(6); // 返回 s.end()
    if (it == s.end()) {
        cout << "未找到不小于 6 的元素" << endl;
    }

    return 0;
}
  1. upper_bound

定义

  • upper_bound 返回指向集合中第一个大于目标值的元素的迭代器。

语法

  • 对于集合 setmultiset
    set<int>::iterator it = s.upper_bound(value);
    
  • 对于数组或容器:
    auto it = upper_bound(arr, arr + n, value);
    

返回值

  • 返回一个迭代器,指向第一个大于 value 的元素。
  • 如果所有元素都小于或等于 value,返回集合的末尾迭代器 end()

用途

  • 用于查找大于目标值的最小元素。
  • 常用于范围查询,确定目标值的上界。

示例

点击查看代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    set<int> s = {1, 2, 3, 4, 5};
    auto it = s.upper_bound(3); // 返回指向 4 的迭代器
    cout << *it << endl; // 输出 4

    it = s.upper_bound(5); // 返回 s.end()
    if (it == s.end()) {
        cout << "未找到大于 5 的元素" << endl;
    }

    return 0;
}
  1. lower_boundupper_bound 的区别
函数 返回值 用途
lower_bound 第一个不小于目标值的元素的迭代器 查找不小于目标值的最小元素
upper_bound 第一个大于目标值的元素的迭代器 查找大于目标值的最小元素
  1. 在不同数据结构中的应用
  • 集合(setmultiset

    • lower_boundupper_bound 是成员函数,可以直接调用。
    • 集合是有序的,因此这些函数的时间复杂度是 O(log n)。
  • 数组或容器

    • lower_boundupper_bound 是算法库中的函数,需要包含头文件 <algorithm>
    • 容器必须是有序的,否则结果不正确。
    • 时间复杂度是 O(log n)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> arr = {1, 2, 3, 4, 5};

    // 使用 lower_bound
    auto it_lower = lower_bound(arr.begin(), arr.end(), 3);
    cout << "lower_bound: " << *it_lower << endl; // 输出 3

    // 使用 upper_bound
    auto it_upper = upper_bound(arr.begin(), arr.end(), 3);
    cout << "upper_bound: " << *it_upper << endl; // 输出 4

    return 0;
}
  • lower_bound:查找第一个不小于目标值的元素。
  • upper_bound:查找第一个大于目标值的元素。
  • 两者在有序集合或数组中非常有用,可以高效地进行查找和插入操作。

1.简单检验答案

  • 整数
    P1873 [COCI 2011/2012 #5] EKO / 砍树
    每个树的高度\(tree[i]\),得到的木材是所有数高于H的树的部分即

    \(ans=sum_{i=1}^{n}(tree[i]-H)\)

    ans随H的增高具有单调递减性

  • 实数
    AcWing5048. 无线网络
    二分半径即可,注意精度即可

     while((rx-lx)>1e-7){//这样处理可以满足精度要求
    

    题解

2.二分+前缀和

AcWing 102. 最佳牛围栏
其实二分答案难的不是二分,难得是怎么check

在给定条件下,计算围起区域内每块地包含的牛的数量的平均值可能的最大值是多少。

显而易见二分平均值,寻找是否存在一个区间满足二分平均值,满足则提高枚举左边界,否则降低平均值

做法:双指针+前缀和,左指针找最小值,寻找是否存在这样一个区间

3.二分+最短路

P8794 [蓝桥杯 2022 国 A] 环境治理

思路:二分答案之后使用floyed来计算最后统计灰尘度.

  • 提示我们该用floyed了
    • 首先数据范围较小n<=100
    • 其次给定的图是完全图,非常稠密
      综上来看用floyed完全足够

直接的暴力是\(o(n^4)\),易超时

而我们可以知道的是某天之后,一定灰尘度达到最小,答案明显具有单调性/二段性

所以用二分答案即可

点击查看代码
#include<bits/stdc++.h>

using namespace std;

const int maxn=1e2+10;
using ll=long long ;
ll f[maxn][maxn];
ll lim[maxn][maxn];
ll d[maxn][maxn];
ll n;
ll p=0,q;
bool check(int day){
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			f[i][j]=d[i][j];
	for(int i=1;i<=n;++i){
		ll val=day/n+(day%n>=i?1:0);
		for(int j=1;j<=n;++j){
			f[i][j]-=val;
			f[i][j]=max(f[i][j],lim[i][j]);
			//cout<<f[i][j]<<" "; 
			f[j][i]-=val;
			f[j][i]=max(f[j][i],lim[j][i]);
		}
	//	cout<<endl;
		
	}

	
	
	for(int k=1;k<=n;++k)
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j)
				f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
	p=0;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			p+=f[i][j];
	return p<=q; 
}
int main(){
	cin>>n>>q;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			cin>>d[i][j];
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			cin>>lim[i][j];
 	ll l=0,r=1e5*n;
 	while(l<=r){
 		ll mid=(l+r)>>1;
 		if(check(mid))
			r=mid-1;
 		else  l=mid+1;
	 }
	if(check(l)==0) cout<<"-1"<<endl;
	else cout<<l<<endl;
	return 0;
}

P1462 通往奥格瑞玛的道路

在所有可以到达奥格瑞玛的道路中,对于每条道路所经过的城市收费的最大值的最小值为多少(经典二分设问方式)

这一句话就是题关键

  • 收费是单调的
  • 检验方式:就是是否可达(根据题目要求写最短路就行,防止血量不够死去

AcWing 340. 通信线路

二分线路花费

check角度就是寻找是否存在k条的线路小于二分的花费,小于k的边贡献为1,否则为0,用dijkstra找到最短路检验即可
\(\\\)
此时可以用双端队列代替堆,+1入队尾,+0入队首,保证单调性,降低时间复杂度
(PS:此题还可以用分层图)

posted @ 2024-10-15 22:13  归游  阅读(57)  评论(0)    收藏  举报