李超树
李超数支持动态插入线段/直线,查询单点极值。
算法思想
排除不可能成为最优解的,维护在当前区间能成为最优解的线段,即该线段在当前区间的某个取值上有最优解。
查询的时间复杂度是 \(O(\log n)\) 的,修改时通过替换和下放,也能达到 \(O(\log n)\) 的复杂度,区间修改能达到 \(O(\log^2 n)\),(证明:最多把每条直线的值域分到 \(\log n\) 个区间,每个区间最多把标记下传 \(\log n\)层,所以区间修改的复杂度为 \(O(\log ^2 n\))。
对信息进行维护时,可以使用斜率和线段的中点取值进行分类讨论。( \(eg\) :
(在类似于这样的图上面插入新线段

设当前区间为 \([L,R]\) ,当前区间的中点为 \(mid\)。
-
当前区间没有线段,新鲜段直接放入该区间。
-
新线段的斜率小于旧线段的斜率:

- 若新线段在中点处的取值 \(>\) 旧线段的取值,则新线段在 \([L,mid]\) 上的取值一定比旧线段更优,但旧线段可能在 \([mid+1,R]\) 上的取值更优,用新线段替换当前节点的旧线段,将旧线段下放至 \([mid+1,R]\) 区间并递归更新答案。
- 若新线段在中点处的取值 \(<\) 旧线段的取值,则旧线段在 \([mid+1,R]\) 上的取值一定比新线段更优,但新线段可能在 \([L,mid]\) 上的取值更优,将新线段下放至 \([L,mid]\) 区间并递归更新答案。
-
新线段的斜率大于旧线段的斜率:
- 若新线段在中点处的取值 \(>\) 旧线段的取值,则新线段在 \([mid+1,R]\) 上的取值一定比旧线段更优,但旧线段可能在 \([L,mid]\) 上的取值更优,用新线段替换当前节点的取值,将旧线段下放至 \([L,mid]\) 区间并递归更新答案。
- 若新线段在中点处的取值 \(<\) 旧线段的取值,则旧线段在 \([L,mid]\) 上的取值一定比新线段更优,但新线段可能在 \([mid+1,R]\) 上的取值更优,将新线段下放至 \([mid+1,R]\) 区间并递归更新答案。
-
新旧线段斜率相同,则比较截距 \(b\),截距大的取值更优,直接用截距大的。
例题
李超树维护线段。
code!
const int N=1e5+10;
const int mod=1e9;
const double eps=1e-9;//误差
int n,op,lans,x,x_1,x_2,y_1,y_2,cnt,t[N<<2];
double k[N],b[N];
void get_val(int xx,int yy,int X,int Y){//计算斜率和截距
++cnt;
if(xx==X){
k[cnt]=0; b[cnt]=max(yy,Y);
}
else {
k[cnt]=1.0*(Y-yy)/(X-xx); b[cnt]=Y-k[cnt]*X;
}
return ;
}
int cmp(double x,double y){
if(x-y>eps) return 1;
if(y-x>eps) return -1;
return 0;
}
double calc(int id,int v){//计算值
return k[id]*v+b[id];
}
void upd(int k,int l,int r,int u){
int &v=t[k],mid=(l+r)>>1;
int line_k=cmp(calc(u,mid),calc(v,mid));
if(line_k==1||(!line_k&&u<v)) swap(u,v);
int vl=cmp(calc(u,l),calc(v,l)),vr=cmp(calc(u,r),calc(v,r));
if(vl==1||(!vl&&u<v)) upd(k<<1,l,mid,u);
if(vr==1||(!vr&&u<v)) upd(k<<1|1,mid+1,r,u);
}
void update(int k,int l,int r,int fl,int fr,int nm){
if(l>=fl&&r<=fr){
upd(k,l,r,nm);
return ;
}
int mid=(l+r)>>1;
if(fl<=mid) update(k<<1,l,mid,fl,fr,nm);
if(fr>mid) update(k<<1|1,mid+1,r,fl,fr,nm);
return ;
}
pdi Max(pdi x,pdi y){
int f=cmp(x.first,y.first);
if(f==1) return x;
if(f==-1) return y;
return x.second<y.second?x:y;
}
pdi query(int k,int l,int r,int v){
if(r<v||l>v) {return {0,0};}
int mid=(l+r)>>1;
double vl=calc(t[k],v);
if(l==r) {return {vl,t[k]};}
return Max({vl,t[k]},Max(query(k<<1,l,mid,v),query(k<<1|1,mid+1,r,v)));
}
signed main(){
n=read();
for(int i=1;i<=n;++i){
op=read();
if(op==1){
x_1=read();y_1=read();x_2=read();y_2=read();
x_1=(x_1+lans-1+39989)%39989+1;
x_2=(x_2+lans-1+39989)%39989+1;
y_1=(y_1+lans-1+mod)%mod+1;
y_2=(y_2+lans-1+mod)%mod+1;
if(x_1>x_2) {swap(x_1,x_2); swap(y_1,y_2);}
get_val(x_1,y_1,x_2,y_2);
update(1,1,N,x_1,x_2,cnt);
}
else {
x=read();
x=(x+lans-1+39989)%39989+1;
cout<<(lans=query(1,1,N,x).second)<<'\n';
}
}
return 0;
}
李超树维护直线。
code!
const int N=1e5+10;
const int M=5e4+1;
const double eps=1e-9;
int n,x,cnt,t[N<<2];
double k[N],b[N];
string s;
int cmp(double x,double y){
if(x-y>eps) return 1;
if(y-x>eps) return -1;
return 0;
}
double calc(int id,int v){
return k[id]*(v-1)+b[id];
}
void update(int x,int l,int r,int v){
if(l==r){if(calc(t[x],l)<calc(v,l)) t[x]=v; return ;}
if(!t[x]) {t[x]=v;return ;}
else {
int mid=(l+r)>>1,u=t[x];
if(k[u]>k[v]){
if(cmp(calc(u,mid),calc(v,mid))==1) update(x<<1,l,mid,v);
else {update(x<<1|1,mid+1,r,t[x]);t[x]=v;}
}
else if(k[u]<k[v]){
if(cmp(calc(u,mid),calc(v,mid))==1) update(x<<1|1,mid+1,r,v);
else {update(x<<1,l,mid,t[x]);t[x]=v;}
}
else if(b[v]>b[u]) t[x]=v;
}
return ;
}
double query(int x,int l,int r,int v){
if(l==r){return calc(t[x],l);}
double res=calc(t[x],v);
int mid=(l+r)>>1;
if(v<=mid) res=max(res,query(x<<1,l,mid,v));
else res=max(res,query(x<<1|1,mid+1,r,v));
return res;
}
signed main(){
n=read();
for(int i=1;i<=n;++i){
cin>>s;
if(s[0]=='P'){
++cnt;
cin>>b[cnt]>>k[cnt];
update(1,1,M,cnt);
}
else {
x=read();
cout<<(int)query(1,1,M,x)/100<<'\n';
}
}
return 0;
}
码量巨大,非常难调/ll。
思路大概是,树剖维护链,然后大力推式子,用李超树维护最小值。
式子是:
\(ans=max(a\times(dis[s]-dis[i])+b)\)
可以发现上述式子是一次函数,于是我们可以求出两点间的 \(LCA\) ,分别进行维护
\(a\times dis[s]-a\times dis[i] \Rightarrow - a \times dis[i] + a \times dis[s]\)
斜率为 \(-a\),截距为 \(a\times dis[s]\)。
\(a \times dis[s] - a\times dis[lca] + a\times dis[i] - a\times dis[lca] \Rightarrow a\times dis[i] + a\times(dis[s]-2\times dis[lca])\)
斜率为 \(a\),截距为 \(a\times(dis[s]-2\times dis[lca])\)。
code:
const int N=1e5+10;
const int inf=123456789123456789;
const int eps=1e-9;
int n,m,op,x,y,z,e,cnt,tot,head[N],t[N<<4],k[N<<4],b[N<<4],mn[N<<4];
int num,o[N],top[N],son[N],siz[N],bel[N],dep[N],fa[N],dis[N];
struct node{
int u,v,w,nxt;
}a[N<<1];
void add(int u,int v,int w){
a[++cnt]=(node){u,v,w,head[u]};
head[u]=cnt;
}
int clac(int id,int v) {
return k[id]*dis[bel[v]]+b[id];
}
int cmp(int x,int y) {
if(x-y>eps) return 1;
if(y-x>eps) return -1;
return 0;
}
void push_up(int x,int l,int r) {//
if(l==r) {mn[x]=min(mn[x],min(clac(t[x],l),clac(t[x],r)));}
else {mn[x]=min(min(clac(t[x],l),clac(t[x],r)),min(mn[x],min(mn[x<<1],mn[x<<1|1])));}
}
void upd(int x,int l,int r,int v){
int &u=t[x],mid=(l+r)>>1;
if(clac(v,mid)<clac(u,mid)) swap(u,v);
if(clac(v,l)<clac(u,l)) upd(x<<1,l,mid,v);
if(clac(v,r)<clac(u,r)) upd(x<<1|1,mid+1,r,v);
}
void update(int x,int l,int r,int fl,int fr,int v) {
if(l>=fl&&r<=fr) {
upd(x,l,r,v);
push_up(x,l,r);
return ;
}
int mid=(l+r)>>1;
if(fl<=mid) update(x<<1,l,mid,fl,fr,v);
if(fr>mid) update(x<<1|1,mid+1,r,fl,fr,v);
push_up(x,l,r);
}
void Update(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
update(1,1,n,o[top[x]],o[x],tot);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
update(1,1,n,o[x],o[y],tot);
}
int query(int x,int l,int r,int fl,int fr){
if(l>=fl&&r<=fr) {
return mn[x];
}
int mid=(l+r)>>1,res=inf;
if(b[t[x]]!=inf) res=min(res,min(clac(t[x],max(fl,l)),clac(t[x],min(fr,r))));//
if(fl<=mid) res=min(res,query(x<<1,l,mid,fl,fr));
if(fr>mid) res=min(res,query(x<<1|1,mid+1,r,fl,fr));
return res;
}
int Query(int x,int y){
int res=inf;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
res=min(res,query(1,1,n,o[top[x]],o[x])),x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
return min(res,query(1,1,n,o[x],o[y]));
}
void build(int x,int l,int r){
t[x]=1;mn[x]=inf;
if(l==r) return ;
int mid=(l+r)>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
return ;
}
int dfs1(int x,int fat){
dep[x]=dep[fat]+1;
fa[x]=fat;
siz[x]=1;
int Son=-1;
for(int i=head[x];i;i=a[i].nxt){
int v=a[i].v;
if(v==fat) continue;
dis[v]=dis[x]+a[i].w;
siz[x]+=dfs1(v,x);
if(siz[v]>Son) {
son[x]=v;
Son=siz[v];
}
}
return siz[x];
}
void dfs2(int x,int topf){
top[x]=topf;
o[x]=++num;
bel[num]=x;
if(!son[x])
return ;
dfs2(son[x],topf);
for(int i=head[x];i;i=a[i].nxt){
int v=a[i].v;
if(!o[v])
dfs2(v,v);
}
}
int LCA(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
return x;
}
signed main(){
n=read(); m=read();
for(int i=1;i<n;++i) {
x=read(); y=read(); z=read();
add(x,y,z);
add(y,x,z);
}
dfs1(1,0);
dfs2(1,1);
k[++tot]=0; b[tot]=inf;
build(1,1,n);
for(int i=1;i<=m;++i) {
op=read();
if(op==1) {
x=read();e=read();y=read();z=read();
int lca=LCA(x,e);
k[++tot]=-y;b[tot]=y*dis[x]+z;Update(x,lca);
k[++tot]=y;b[tot]=y*(dis[x]-(2*dis[lca]))+z;Update(e,lca);
}
else {
x=read();e=read();
cout<<Query(x,e)<<'\n';
}
}
return 0;
}
未完待续(?

浙公网安备 33010602011771号