斜率优化DP||李超线段树总结

让写总结时才发现 DP 忘完了,遂有此。


前置知识:

单调队列优化 DP


斜率优化

为什么叫这个名字?

因为其推导过程中有一个式子像斜率的表达式。

我们先从一个转移开始:

\[{dp_i=min(dp_j+(a_i-a_j)^2+c)} \]

其中 \(a\) 单调递增。

对上式化简得:

\[{dp_i=c+a_i^2+min(dp_j+a_j^2-2a_ia_j)} \]

\(dp_j+a_j^2 =x_j\) ,\(2a_i=y\),设 \(a<b\),若 \(a\) 要比 \(b\) 有用,即:

\[{x_a-ya_a \le x_b-ya_b} \]

化简,得:

\[{x_a-x_b \le y(a_a-a_b)} \]

再化简,得:

\[ {\frac {x_a-x_b}{a_a-a_b} \ge y} \]

变号原因:\(a<b\), \(a_a<a_b\)

看着特别像斜率,所以叫斜率优化。

我们记 \(get(i,j)\) 表示 $ \Large {\frac {x_i-x_j}{a_i-a_j}}$

假设现在有三个决策点 \(fr,nw,bc\),要想使 \(nw\) 为最优决策点,则:

\(get(fr,nw) \le 2a_i\)

\(get(nw,bc) \ge 2a_i\)

综合两式,有:

\(get(fr,nw) \le get(nw,bc)\)

即,如果不满足上式,则 \(nw\) 一定不优。

利用单调队列的思想:每次在队尾插入一个元素前,比较 \(get(end-1,end)\)\(get(end,now)\) 的大小,如果不满足上式关系,则扔掉队尾,直至满足条件,这样保存下来的两点间的斜率就是单调递增的。

我们每次找到第一个点与之后一个点的斜率大于等于 \(2a_i\) 的地方,因为 \(a_i\) 单调递增,所以每次可以把不满足条件的点丢掉。

写个双端队列即可:

	q.push_back(1);
	for(int i=2;i<=n;i++){
		while(q.size()>1&&get_k(*q.begin(),*(q.begin()+1))<=2*h[i])q.pop_front();
		int j=q.front();
		dp[i]=dp[j]+(h[i]-h[j])*(h[i]-h[j])+c;
		while(q.size()>1&&get_k(*(q.end()-2),*(q.end()-1))>=get_k(*(q.end()-1),i))q.pop_back();
		q.push_back(i);
	}

李超线段树

有人会说:主播主播,你的斜率优化推式子太吃操作了,有没有更无脑的方式呢?

还真有。

我们考虑这样一个问题,每次在平面内插入一条直线,每次询问在 \(x\) 坐标上的最大值。

我们建一个线段树,每个节点维护当前节点中,在 \(x = mid\) 处去到最大的线段,那么每次新加入一条线段我们比较新线段与老线段在区间中点答案谁大,那么我们就拿出更劣的线段,然后看这个线段在左右区间是否可能贡献,然后递归就做完了。

我们分析复杂度:注意到每次最多递归 \(1\) 边,于是复杂度就是 \(O(n \log n)\)

那我们就可以解决这个问题了。

// Problem: P4097 【模板】李超线段树 / [HEOI2013] Segment
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4097
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// Author: Air2011
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ld long double
#define Air
namespace io{inline int read(){int x;cin>>x;return x;}inline void write(int x){cout<<x;}}
using namespace io;
int n;
const int N = 1e5 + 10, Iinf = 1, Ainf = 4e4, INF = 1e9;
const double Eps = 1e-10; 
struct Sgm{
	ld k, b;
	int id;
	pair<ld, int> operator ()(int x){
		return {k * x + b, id};
	}
}seg[N];
int id[N * 4];
bool grt(ld x, ld y){
	if(x - y > Eps) return 1;
	return 0;  
}
bool check(pair<ld, int> x, pair<ld, int> y){// x 比 y 优秀
	if(grt(x.first, y.first)) return 1;
	if(grt(y.first, x.first)) return 0;
	if(x.second < y.second) return 1;
	return 0;
}
void insert(int p, int l, int r, int st){
	if(!id[p]){
		id[p] = st;
		// cerr << "$#$@$ " << id[p] << ' ' << p << endl;
		return ;
	}
	// cerr << p << ' ' << l << ' ' << r << ' ' << st << endl;
	int mid = (l + r) >> 1;
	if(check(seg[st](mid),seg[id[p]](mid))){
		swap(id[p], st);
	}
	if(check(seg[st](l),seg[id[p]](l))){
		insert(p * 2, l, mid, st);
	}
	if(check(seg[st](r),seg[id[p]](r))){
		insert(p * 2 + 1, mid + 1, r, st);
	}
}
void change(int p, int l, int r, int ql, int qr, int st){
	// 
	if(ql <= l && qr >= r){
		// cerr << p << ' ' << l << ' ' << r << ' ' << ql << ' ' << qr << ' ' << st <<  '\n';
		insert(p, l, r, st);
		return ;
	}
	int mid = (l + r) >> 1;
	if(ql <= mid){
		change(p * 2, l, mid, ql, qr, st);
	}
	if(qr > mid){
		change(p * 2 + 1, mid + 1, r, ql, qr, st);
	}
}
pair<ld, int> ask(int p, int l, int r, int x){
	
	if(l == r) return seg[id[p]](l);
	int  mid = (l + r) >> 1;
	pair<ld, int> ans = seg[id[p]](x);
	// cerr << p << ' ' << l << ' ' << r << ' ' << x << endl;
	// cerr << ans.first << ' ' << ans.second << endl;
	if(x <= mid){
		pair<ld, int> temp = ask(p * 2, l, mid, x);
		// cerr << temp.first << ' ' << temp.second << endl;
		if(check(ans, temp)){
			return  ans;
		}
		else{
			return temp;
		}
	}
	else{
		
		pair<ld, int> temp = ask(p * 2 + 1, mid + 1, r, x);
		// cerr << temp.first << ' ' << temp.second << endl;
		if(check(ans, temp)){
			return  ans;
		}
		else{
			return temp;
		}
	}
}
signed main(){
#ifndef Air
	freopen(".in","r",stdin);
	freopen(".out","w",stdout);
#endif
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	n = read();
	int lsans = 0;
	int tot = 0;
	for(int i = 1; i <= n; i++){
		int op = read();
		if(op == 1){
			int l = (read() + lsans - 1) % 39989 + 1;
			ld v1 = (read() + lsans - 1) % INF + 1;
			int r = (read() + lsans - 1) % 39989 + 1;
			ld v2 = (read() + lsans - 1) % INF + 1;
			if(l > r){
				swap(l, r);
				swap(v1, v2);
			}
			if(l == r){
				v1 = max(v1, v2);
				seg[++tot] = {0, v1, tot};
				change(1, Iinf, Ainf, l, r, tot);	
			}
			else{
				// cerr << v1 << ' ' << v2 << ' ' << r - l << endl;
				ld k = (v2 - v1) * 1.0 / (r - l);
				// cerr << k <<  ' ' <<  v1 - k * l << endl;
				seg[++tot] = {k, v1 - k * l, tot};
				change(1, Iinf, Ainf, l, r, tot);	
			}
		}
		else{
			int x = (read() + lsans - 1) % 39989 + 1;
			lsans = ask(1, Iinf, Ainf, x).second;
			// lsans = lsans;
			cout << lsans << '\n';
		}
	}
	return 0;
}
posted @ 2025-02-16 11:35  Air2011  阅读(19)  评论(0)    收藏  举报