三分算法概念
相信大家都对二分思想比较熟悉了,但是三分思想却不一定非常熟悉,因为在日常刷题过程中二分用的比较多一些,比如二分答案或是二分查找。我们都知道,二分思想在使用的过程中是有条件限制的,要求我们数据必须是线性的、单调的。而三分思想和二分的思想几乎一致,但是限制不同,所以解决的问题也不同。
二分可以解决单调问题,也可抽象为一次函数问题;三分可以解决单峰问题,可抽象为二次函数问题。
适用场景
三分算法适用于求解凸性函数的极值问题,二次函数就是一个典型的单峰函数。 二分利用的是函数的单调性,三分算法利用的是函数的单峰性。
如图所示:
在区间 [ l , r ] [l,r] [l,r]中,令 m i d l = l + r − l 3 midl=l+\frac{r-l}{3} midl=l+3r−l, m i d r = r − r − l 3 midr = r-\frac{r-l}{3} midr=r−3r−l,初始时分别位于 1 3 \frac{1}{3} 31和 2 3 \frac{2}{3} 32处,然后计算这两个点的函数值,如果 c h e c k ( m i d l ) > c k e c k ( m i d r ) check(midl)>ckeck(midr) check(midl)>ckeck(midr),求解区间由 [ l , r ] [l,r] [l,r]变为 [ l , m i d r ] [l,midr] [l,midr]。需要注意的是,三分法严格单调,在出现函数值相等的时候该方法将不适用。
模板(三分答案)
//check函数根据题目要求写
while(l <= r) {
int midl = l + (r - l) / 3;
int midr = r - (r - l) / 3;
if(check(midl) <= check(midr)) r = midr - 1;
else l = midl + 1;
}
res = min(check(r), check(l));
例题
AC代码
主要思路已在代码中注释
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
#define ll long long
#define ull unsigned long long
#define PII pair<int, int>
#define lowbit(x) (x & -x)
// #define mid ((l + r) >> 1)
#define ALL(x) (x.begin(), x.end())
#define endl '\n'
#define fi first
#define se second
const int INF = 0x7fffffff;
const int mod = 1e9 + 10;
const int N = 2e5 + 10;
int n;
struct {int p, w, d;} a[N];
ll check(int mid) {
ll res = 0;
for(int i = 1; i <= n; i ++ ) {
int p = a[i].p, w = a[i].w, d = a[i].d;
//在区域内不需要移动
if(mid >= p - d && mid <= p + d) continue;
//计算移动成本
if(mid < p) res += (ll)(p - d - mid) * w;
else res += (ll)(mid - p - d) * w;
}
return res;
}
signed main(void) {
IOS
cin >> n;
for(int i = 1; i <= n; i ++ ) {
int p, w, d; cin >> p >> w >> d;
a[i] = {p, w, d};
}
//三分模板
int l = 0, r = 1e9;
while(l <= r) {
int midl = l + (r - l) / 3;
int midr = r - (r - l) / 3;
if(check(midl) <= check(midr)) r = midr - 1;
else l = midl + 1;
}
cout << min(check(r), check(l)) << endl;
return 0;
}