广义李超线段树
前言:
1.本篇文章为李超线段树的扩展,不会的可以去这里
这是我的模板:
struct LCT { int tot=0,rt=0; struct Tree{int lc,rc,id;}tr[M]; ll Ans(ll z,ll x){return p[z].k*x+p[z].b;} #define ls (tr[p].lc) #define rs (tr[p].rc) #define mi ((l+r)>>1) void Upd(int &p,int l,int r,int u) { if(!p)p=++tot; int &v=tr[p].id; if(Ans(u,mi)>Ans(v,mi))swap(u,v); if(Ans(u,l)>Ans(v,l))Upd(ls,l,mi,u); if(Ans(u,r)>Ans(v,r))Upd(rs,mi+1,r,u); } ll Ask(ll p,ll l,ll r,ll d) { if(!p)return 0; ll ans=Ans(tr[p].id,d); if(l==r)return ans; return max(ans,d<=mi?Ask(ls,l,mi,d):Ask(rs,mi+1,r,d)); } void Clear(int l,int r) { for(int i=1;i<=tot;i++)tr[i].lc=tr[i].rc=tr[i].id=0; tot=rt=0; for(int i=l;i<=r;i++)Upd(rt,0,n,i); } }T[M];
2.此算法是本人在做题时yy出来的,不知道之前有没有出现过
算法:
1.背景:
李超线段树主要用来维护斜率优化题目,相较于维护凸壳,优点在于无脑简单无需分析,代码小清新,缺点在于有些时候时间复杂度会比维护凸壳高。
当题目不再是维护的函数不再是直线时,就只有单调队列/单调栈的做法。
此算法主要是解决维护非直线时的李超线段树做法。
2.实现:
注意到李超线段树中有一个函数 Ans(看模板) 是用来计算函数在一个点的取值。
我们如果将我们要求的函数(非直线)的解析式带入,就解决了问题。
例题1:
把绝对值拆开,正反维护各维护一边
把 看成函数,套上李超线段树求最大值
注意到开根运算是有定义域的,一种无脑的做法就是插入到一段区间内。
巧妙的方法是重定义开根运算,如果遇到负数就设成负无穷。
为什么不会出 bug 呢?注意到询问单调递增且都比所有插入的 都大,那么也就不会访问到开根负数的地方。
代码:

#include<bits/stdc++.h> using namespace std; typedef double db; typedef long long ll; const int N=5e5+3; const db eps=1e-10; int n,rt,a[N],sx[N],sy[N]; struct Fun{int h,g;}p[N]; struct LCT { int tot=0,o=0; struct Tree{int lc,rc,id;}tr[N]; int dcmp(db x,db y){return x-y>eps?1:y-x>eps?-1:0;} db sqr(db x){return dcmp(0,x)==1?-1e9:sqrt(x);} db Ans(int id,int x){return (db)p[id].h+sqr((!o)?x-p[id].g:p[id].g-x);} #define ls (tr[p].lc) #define rs (tr[p].rc) #define mi ((l+r)>>1) void Upd(int &p,int l,int r,int u) { if(!p){p=++tot;tr[p].id=u;return;} int &v=tr[p].id; if(dcmp(Ans(u,mi),Ans(v,mi))==1)swap(u,v); if(dcmp(Ans(u,l),Ans(v,l))==1)Upd(ls,l,mi,u); if(dcmp(Ans(u,r),Ans(v,r))==1)Upd(rs,mi+1,r,u); } db Ask(int p,int l,int r,int d) { if(!p)return 0; db ans=Ans(tr[p].id,d); if(l==r)return ans; return max(ans,d<=mi?Ask(ls,l,mi,d):Ask(rs,mi+1,r,d)); } void Clear() { for(int i=1;i<=tot;i++)tr[i].lc=tr[i].rc=tr[i].id=0; tot=rt=0;o=1; } }T; int main() { cin>>n; for(int i=1;i<=n;i++)cin>>a[i],p[i]={a[i],i}; for(int i=1;i<=n;i++)sx[i]=ceil(T.Ask(1,1,n,i)),T.Upd(rt,1,n,i); T.Clear(); for(int i=n;i>=1;i--)sy[i]=ceil(T.Ask(1,1,n,i)),T.Upd(rt,1,n,i); for(int i=1;i<=n;i++)cout<<max(max(sx[i],sy[i])-a[i],0)<<endl; }
例题二:
https://www.luogu.com.cn/problem/P1912
把 看成函数,套李超线段树。
问题在这个函数的值会非常大,但我们又需要比大小,所以只能请出 long double来解决。
代码:

#include<bits/stdc++.h> using namespace std; typedef __int128 ll; typedef long double ld; typedef pair<ll,ll> pll; #define fi first #define se second const ll N=3e6+3,INF=(ll)1e18; ll n,Len,P,rt,sum[N],sta[N]; pll f[N]; string str[N]; ll read() { ll x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return x*f; } void write(ll X) { if(X<0){putchar('-');X=~(X-1);}int s[20],o=0; while(X){s[++o]=X%10;X/=10;} if(!o)s[++o]=0;while(o)putchar(s[o--]+'0'); putchar('\n'); } struct LCT { ll tot=0; struct Tree{ll lc,rc,id;}tr[N]; ll Abs(ll x){return x>=0?x:-x;} ld Ksm(ld x,ll y){ld s=1;for(ll i=1;i<=y;i<<=1,x=x*x)if(y&i)s=s*x;return s;} ld Ans(ll id,ll x){return f[id].fi+Ksm(Abs(x-sum[id]-1-Len),P);} ll Res(ll id,ll x){return min((ld)INF+1,Ans(id,x));} #define ls (tr[p].lc) #define rs (tr[p].rc) #define mi ((l+r)>>1) void Upd(ll &p,ll l,ll r,ll u) { if(!p){p=++tot;tr[p].id=u;return;} ll &v=tr[p].id; if(Ans(u,mi)<Ans(v,mi))swap(u,v); if(Ans(u,l)<Ans(v,l))Upd(ls,l,mi,u); if(Ans(u,r)<Ans(v,r))Upd(rs,mi+1,r,u); } pll Ask(ll p,ll l,ll r,ll d) { if(!p)return {INF+1,0}; pll ans={Res(tr[p].id,d),tr[p].id}; if(l==r)return ans; return min(ans,d<=mi?Ask(ls,l,mi,d):Ask(rs,mi+1,r,d)); } void Clear() { for(int i=1;i<=tot;i++)tr[i].lc=tr[i].rc=tr[i].id=0; tot=rt=0; } }T; void Solve() { n=read();Len=read();P=read();T.Clear(); for(int i=1;i<=n;i++)cin>>str[i],sum[i]=sum[i-1]+str[i].size(); for(int i=1;i<=n;i++)sum[i]+=i; T.Upd(rt,0,sum[n],0); for(int i=1;i<=n;i++)f[i]=T.Ask(rt,0,sum[n],sum[i]),T.Upd(rt,0,sum[n],i); if(f[n].fi>INF)cout<<"Too hard to arrange"<<endl; else { write(f[n].fi);ll top=0,x=n; while(x)sta[++top]=x,x=f[x].se; sta[++top]=0; for(int i=top;i>1;i--) { for(int j=sta[i]+1;j<sta[i-1];j++)cout<<str[j]<<" "; cout<<str[sta[i-1]]<<endl; } } cout<<"--------------------"<<endl; } int main() { ll T;T=read(); while(T--)Solve(); }
3.使用条件
必须能快速求出函数在某一点的取值,任意两个函数只能有一个交点。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析