二分, 三分

二分, 三分

二分

算法简介:
有n个数a1,a2,,an和m个 询问每次询问一个数,需要回答a1,a2,,an里有多少个数字小于x

二分可以解决问题的共同点?
数组中存在一条分界线,使得分界线左边的位置都满足某个条件且右边位置不满足某个条件(这个条件会出现在模板中的if条件中)

在这里首先要把a数组从小到大排序

模板:

int calc(int x)
{
	int L = 0, R = n+1;
	while(L + 1 < R)
	{
		int mid = (L + R) >> 1;
		if(a[mid] < x) L = M;
		else R = M; 
	}
	return L;
}

二分其他问题

  1. 有n个数a1,a2,,an和m个询问.每次询问一个数x,问x在数组中有没有出现过

在使用二分找到分界线,恰好位于分界线两边的数字有什么特殊的地方?

对于以上模板,左边那个是小于x里最大的,右边的那个是大于等于x里最小的

判断边界右边是否等于x即可

  1. 找到一个整数x,使得小于等于x的整数都满足某个条件且大于x的整数都不满足某个条件;
    此时x即为满足这个条件最大的整数
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <array>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N=1e5+10;
int n,m;
int k[N],b[N];
int calc(int x)
{
	int res = 0;
	for(int i = 1; i <= n; i++)
	{
		if(x >= b[i]) res += (x - b[i]) / k[i] + 1;
	}
	return res;
}
LL sum(int x)
{
	LL res = 0;
	for(int i = 1; i <= n; i++)
	{
		if(x >= b[i])
		{
			int y = (x - b[i]) / k[i] + 1;
			res += 1LL * (b[i] + (y - 1) * k[i] + b[i]) * y / 2;
		}
	}
	return res;
}
int main()
{
	scanf("%d",&n);
	for(int i = 1; i <= n; i++) scanf("%d%d",&k[i],&b[i]);
	scanf("%d",&m);
	int L = 0, R = 1e9; // L要选择0,若选1则可能出现极端情况类似于全1数列,答案可能错误
	while(L + 1 < R)
	{
		int mid = (L + R) >> 1;
		// 此时左侧最靠近边界的是数列中大小小于m的数的个数总和小于m的最大值
		if(calc(mid) <= m) L = mid; //calc函数计算小于mid的数有多少个
		else R = mid;
	}
	// printf("%d %d\n",L,calc(L));
	// sum求总和,(m-calc(L))得到还要往后取多少个数因为可能会出现取同一个数但不能把这个数出现次数全取的情况
	printf("%lld\n", sum(L) + 1LL * (m - calc(L)) * (L + 1));

	return 0;
}
  1. 当2中求x是浮点数怎么办?
当小数时应避免使用while循环而是使用固定次数的for代替,否则可能有精度误差
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <array>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N = 1e5+10;
int n;
int a[N], b[N];
bool check(double x)
{
	double L = -1e10, R = 1e10;
	for(int i = 1; i <= n; i++)
	{
		double l = a[i] - b[i] * x, r = a[i] + b[i] * x;
		L = max(L, l);
		R = min(R, r);
	}
	if(L > R) return false;
	return true;
}
int main()
{
	scanf("%d",&n);
	for(int i = 1; i <= n; i++) scanf("%d%d", &a[i], &b[i]);
	double L = 0, R =50000;
	for(int i = 1; i <= 100; i++)
	{
		double mid = (L + R) / 2;
		if(check(mid)) R = mid;
		else L = mid;
	}
	printf("%.10f\n",R);

	return 0;
}


二分 + 哈希

#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <array>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N = 2e5+10;
int b1 = 1037, b2 = 1111, p1 = 9999971, p2 = 9999973;
int n, a[N], s[N];
LL c1[N],c2[N],h1[N],h2[N],H1[N],H2[N];
PII calc1(int l,int r)
{
	return {(h1[r] - h1[l-1] * c1[r - l + 1] % p1 + p1) % p1, (h2[r] - h2[l-1] * c2[r - l +1] % p2 + p2) % p2};
}
PII calc2(int l,int r)
{
	return {(H1[l] - H1[r+1] * c1[r - l + 1] % p1 + p1) % p1, (H2[l] - H2[r+1] * c2[r - l +1] % p2 + p2) % p2};
}
int main()
{
	scanf("%d",&n);
	for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
	int m = 0;
	s[++m] = 1001;
	for(int i = 1; i <= n; i++) s[++m] = a[i], s[++m] = 1001;
	c1[0] = c2[0] = 1;
	for(int i = 1; i <= m; i++)
		c1[i] = c1[i-1] * b1 % p1, c2[i] = c2[i-1] * b2 % p2;
	for(int i = 1; i <= m; i++)
		h1[i] = (h1[i-1] * b1 % p1 + s[i]) % p1,
		h2[i] = (h2[i-1] * b2 % p2 + s[i]) % p2;
	for(int i = m; i; i--)
		H1[i] = (H1[i+1] * b1 % p1 + s[i]) % p1,
		H2[i] = (H2[i+1] * b2 % p2 + s[i]) % p2;
	int ans = 0;
	for(int i = 1; i <= m; i++)
	{
		int L = 0, R = min(i, m - i + 1) + 1;
		while(L + 1 < R)
		{
			int mid = (L + R) / 2;
			if(calc1(i, i + mid - 1) == calc2(i - mid + 1, i)) L = mid;
			else R = mid;
		}
		ans = max(ans, L);
	}
	printf("%d\n", ans-1);

	return 0;
}
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <array>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N = 1e5+10;
int n,a[N],c[N],y;
// 这里c数组维护最长上升序列的每一位,每一位必须尽可能的保证小才能最大化可以加入数字的数量
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	y=0;
	for(int i=1;i<=n;i++)
	{
		if(a[i]>c[y]) c[++y] = a[i];
		else
		{
			int L = 0, R = y;
			while(L+1 < R)
			{
				int mid = (L+R)/2;
				if(c[mid]<a[i]) L=mid;
				else R=mid;
			}
			c[R]=a[i];
		}
	}
	cout<<y<<endl;
	
	return 0;
}


三分

三分与二分类似,也是通过不断缩小答案可能的区间范围,来找到最终答案所在的位置

三分一般用来查找某类单峰函数的峰值

  1. 对于开口向下的单峰函数f,假设现在取到峰值x的可能范围是[L,R]

取三等分点M1=(R - L)/3+L, M2 = (R-L)/3*2+L

image.png

此时令 L = M1,继续三分即可

同理,假如f(M1) >= f(M2),令R = M2, 继续三分即可

  1. 开口向上单峰函数类似

不可三分的单峰函数

某些函数可能是单峰的,但是峰值两边并非是严格递增(递减)的,即有可能出现"平台"(一条与x轴平行的直线)

此时f(M1)==f(M2)时以下情况无法分辨

image.png

模板

double f(x) {……}

double calc()
{
	double L = 0, R = 1e10;
	for(int i = 1; i <= 100; i++)
	{
		double M1 = L + (R - L) / 3;
		double M2 = L + (R - L) /3 * 2;
		if(f(M1) < f(M2)) L = M1;
		else R = M2;
	}
	return f(L);
}
posted @   viewoverlook  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
欢迎阅读『二分, 三分』
点击右上角即可分享
微信分享提示