浅谈李超线段树
众所周知,\(Li\ Chao\ Tree=LCT=Link\ Cut\ Tree\)。
在我们的日常学习生活中,经常会遇到以下问题:
维护一种数据结构,要求:
- 添加一条线段
- 求解 \(x=k\) 与所有线段交点中,\(y\) 最大的一个。
众所周知,线段会影响一个区间的答案。区间取 \(max+\) 单点最大值,想到线段树。
但是该怎么修改哩?
我们设 \(dt_{l,r}\) 表示完全覆盖 \([l,r]\) 的所有线段中,与 \(x=mid\) 的交点 \(y\) 值最大的那条线段的编号。
那么发现如下性质:
- 假如新线段与老线段相比,\(x=l\) 和 \(x=r\) 时都更优,那么新线段在整个区间中都完爆老线段。
- 假如新线段与老线段相比,\(x=l\) 和 \(x=r\) 时都更劣,那么老线段在整个区间中都完爆新线段。
这两种情况直接改变 \(dt_{l,r}\) 的值就可以了,假如用标记永久化就可以 \(return\) 掉了。
发现假如不符合上述性质,且 \(x=mid\) 时新线段优于老线段,那么可以理解为原来是新线段,加入老线段,直接交换即可。
那么我们现在考虑的问题就是:新线段有可能在哪一段比原先更优。
发现假如 \(x=l\) 时新线段更优,那么只有在 \([l,mid]\) 里,新线段才可能更优,反之亦然。这个手模很好理解。
那么用线段树实现这个过程就好了。
分析时间复杂度:
由于对 \(dt\) 的定义,导致我们必须找到所有被新线段包含区间(数量级为 \(O(\log n)\)),再进行类似单点修改的操作,所以时间复杂度轻轻松松 \(O(n\log^2n)\)。
特别的,当插入的是直线时,第一步的数量级变为 \(O(1)\),所以时间复杂度为 \(O(n\log n)\)。
#include<bits/stdc++.h>
#define int long long
#define $ inline
using namespace std;
const int N=2e5+5,q=39989,p=1e9;
const double eps=1e-8;
int n,la=-1,id;
double k[N],b[N];
int dcmp(double x){
return fabs(x)<=eps?0:x<0?-1:1;
}double f(int x,int y){
return k[x]*y+b[x];
}int ju(int x,int y,int p){
double fx=f(x,p),fy=f(y,p);
return dcmp(fx-fy)?fx<fy:x>y;
}struct Lichao{
int dt[N];
$ void upd(int x,int l,int r,int L,int R,int v){
if(L<=l&&r<=R){
if(ju(v,dt[x],l)&&ju(v,dt[x],r)) return;
if(ju(dt[x],v,l)&&ju(dt[x],v,r)) return dt[x]=v,void();
int mid=(l+r)>>1;
if(ju(dt[x],v,mid)) swap(dt[x],v);
if(ju(dt[x],v,l)) upd(x<<1,l,mid,L,R,v);
else upd(x<<1|1,mid+1,r,L,R,v);
return;
}int mid=(l+r)>>1;
if(L<=mid) upd(x<<1,l,mid,L,R,v);
if(R>mid) upd(x<<1|1,mid+1,r,L,R,v);
}$ int que(int x,int l,int r,int v){
if(l==r) return dt[x];
int mid=(l+r)>>1,re=0;
if(v<=mid) re=que(x<<1,l,mid,v);
else re=que(x<<1|1,mid+1,r,v);
return (!ju(re,dt[x],v))?re:dt[x];
}
}lcst;//Link Cut Tree=Li Chao Tree?
void t(int &x,int qp){
x=(x+la+qp)%qp+1;
}void solve0(){
int xa,ya,xb,yb;
cin>>xa>>ya>>xb>>yb;
t(xa,q),t(xb,q),t(ya,p),t(yb,p);
if(xb<xa) swap(xa,xb),swap(ya,yb);
if(xa==xb) k[++id]=0,b[id]=max(ya,yb);
else k[++id]=1.0*(yb-ya)/(xb-xa),b[id]=yb-k[id]*xb;
lcst.upd(1,1,4e4,xa,xb,id);
}void solve1(){
int k;cin>>k,t(k,q);
la=lcst.que(1,1,4e4,k);
cout<<la<<"\n",la--;
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
int opt;cin>>opt;
if(!opt) solve1();
else solve0();
}return 0;
}//基本逻辑:寻找区间中点最优解