三分查找

我们知道二分查找适用于存在单调性的问题中,但如果我们的所求的问题不单调,而是先增后减的凸函数呢?
于是我们就可以使用三分求这种函数的极值

点击查看代码
int solve(int l, int r)
{
	// 三分求凸函数的最大值 
	while(r - l > 2)
	{
		int ml = l + (r - l + 1) / 3;
		int mr = r - (r - l + 1) / 3;
		if(f(ml) < f(mr)) l = ml; // 如果是求凹函数最小值,这里改为 f(ml) > f(mr)
		else r = mr;
	}
	int ans = l; // 最大值下标 
	for(int i = l; i <= r; i++)
	{
		if(f(i) > f(ans)) ans = i;
	}
	return ans;
}

Note: 三分也可以用来求单调函数的最值

也就是说二分能干的事情三分也可以干

例题一 CF2132E
首先对两人的卡片大小从大到小排序,令\(suma[i]\), \(sumb[i]\),分别为两人卡片值的前缀和
考虑第一个人选前\(u\)个数的情况,这里\(max(z- y, 0) \leq u \leq min(x, z)\)
两人卡片总和\(f(u) = suma[u] - sum[z-u]\)
发现这\(f(u)\)是个先增后减的凸函数,考虑直接对其三分求最大值
代码如下

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

using namespace std;
const int MAXN = 200010;
int T, n, m, q;
long long a[MAXN], b[MAXN], suma[MAXN], sumb[MAXN];
bool cmp(const long long & a, const long long & b)
{
	return a > b;
}
long long solve(int x, int y, int z)
{
	int l = max(0, z - y), r = min(x, z);
	while(r - l > 2)
	{
		int ml = l + (r - l + 1) / 3;
		int mr = r - (r - l + 1) / 3;
		if(suma[ml] + sumb[z-ml] < suma[mr] + sumb[z-mr]) l = ml;
		else r = mr;
	}
	long long ans = 0;
	for(int i = l; i <= r; i++)
	{
		ans = max(ans, suma[i] + sumb[z-i]);
	}
	return ans;
}


void init()
{
	for(int i = 1; i <= max(n, m); i++) suma[i] = sumb[i] = 0;
}

int main()
{
	ios::sync_with_stdio(0), cin.tie(0);
	cin >> T;
	while(T--)
	{
		cin >> n >> m >> q;
		init();
		for(int i = 1; i <= n; i++)
		{
			cin >> a[i];
		}
		sort(a + 1, a + 1 + n, cmp);
		for(int i = 1; i <= m; i++)
		{
			cin >> b[i];
		}
		sort(b + 1, b + 1 + m, cmp);
		for(int i = 1; i <= n; i++) suma[i] = suma[i-1] + a[i];
		for(int i = 1; i <= m; i++) sumb[i] = sumb[i-1] + b[i];
		while(q--)
		{
			int x, y, z;
			cin >> x >> y >> z;
			printf("%lld\n", solve(x, y, z));
		}
	}
	return 0;
}

三分也可转化为二分函数增量

\(f(x)\)\([L, R]\)上先增后减的凸函数.极值点为\(x_0\)
\([L, x_0]\)\(f(x) - f(x-1) \geq 0\),在\([x_0 + 1, R]\)\(f(x) - f(x-1) < 0\)
因此我们只需要二分找到满足\(f(t) - f(t-1) \geq 0\)的最后一个\(t\)

点击查看代码
int calc(int l, int r)
{	
if(f(r) - f(r-1) >= 0) return r;	
	while(l + 1 != r)
	{
		int mid = (l + r) / 2;
		if(f(mid) - f(mid - 1) >= 0) l = mid;
		else r = mid; 
	}
	return l;
}

还有一件事

请注意三分只适用于在\([L, R]\)上的单峰函数

单峰函数是在所考虑的区间中只有一个严格局部极大值(峰值)的实值函数。如果函数f(x)在区间[a, b]上只有唯一的最大值点C,而在最大值点C的左侧,函数单调增加;在点C的右侧,函数单调减少,则称这个函数为区间[a, b]上的单峰函数。

根本原因在于我们的三分函数不能精确的收缩到包含所有极值的区间

posted @ 2025-11-11 23:04  东东哥本人  阅读(3)  评论(0)    收藏  举报