李超线段树 学习笔记
笔记仅记录个人理解,不是面向他人的入门教程.
昨天做到了这个题:BZOJ4515 [SDOI2016]游戏。发现自己根本不会做,这线段树怎么维护啊。然后才知道有这么个李超线段树。。
标记永久化
李超线段树里面有一个标记永久化的思想,但是我没写过这种题。。大概看了一下。
- 树套树以及主席树里,区间操作打标记会显得十分的麻烦
- 可以将lazytag固定,不下传,不pushup的做法是标记永久化
- 具体可以参考下面那个网址
- 个人觉得最值得注意的是是一路修改还是pushup
- 求和的时候有可能pushup儿子的时候儿子处在一个永久化标记下面,他部分信息没有被更新,会出问题,非要用pushup的话,必须累计上走过路线的贡献,及时一路更新更为简便。
- 求最值的时候就必须需要pushup,但是注意要把上面累计上的标记一起放到pushup里面。
可以参考这位的blog。
李超线段树
李超线段树的基本功能:
- 在线动态在平面直角坐标系中插入一些线段/直线
- 查询与$x=x_0$相交的所有直线里面$y$最小/最大的一个
- 一些魔改操作,如区间查询、树上修改,详见下面。
板子题:BZOJ3165 [HEOI2013]Segment
- 如何维护最大值?李超线段树是这样维护线段树信息的:设线段树节点代表横坐标区间,那么节点上维护在这个区间上所有线段中,暴露在最上面的,具体来说,也就是在区间中点处最高的线段,不妨称之为优势线段。记录其$k,b$。
- 现在假设第一次插入一条线段,先把他在线段树上拆分成若干个完整的区间,这些区间分别处理。在一个区间中,发现没有优势线段,直接插入在这个区间上,不向下递归,立即回去。
- 可以猜到,这个区间内的某个点查询最大值,答案在这个区间节点上。
- 现在考虑如果一个区间已经维护了一个线段,现在又插入了该怎么办。
- 分几类情况讨论。
- 如果这个新线段整个比旧线段低,肯定不会成为最优解,直接退回。
- 比旧线段高,那原来这个线段就没用了,修改之后退回。
- 否则,会有交点。交点在mid坐标左边,那么:
- 斜率比旧线段大:显然新线段成为了最优势线段,把维护的线段改掉。但是,原来旧线段并不能否定毫无作用,因为左侧交点左边还可能更大,所以我们先把当前旧线段标记下放到左半区间。
- 斜率比旧线段小:新线段不会成为最优势线段,右半区间不优,左半区间仍可能在交点左侧产生更大值,于是把新线段扔到左半区间继续处理。
- 交点在mid坐标右边,那么:
- 斜率比旧线段大:只把新线段下放到右半区间处理。
- 斜率比旧线段小:把旧线段下放到右半区间,新线段取代之成为当前区间最优势线段。
- 总的来说,其实就是讨论谁mid的值更大,然后把当前区间较劣势线段下放至交点一侧的区间在修改。
- 说的不太清楚,可以结合图,看一下这位的配图:神仙
- 为什么要这样做?下放标记原理在于,当前维护的线段假如是区间内所有线段的最优解,插入了一个新线段之后,有部分点最优解改变。那么为了保证正确维护这些点的最大值,我们不得不将一部分信息下放到对应区间,保留住最大值,查询点的时候,所有包含这个点的区间,或多或少维护了一些线段,其中必定有一个可以产生点$x$上的最大值。这里使用了标记永久化思想,只在整块区间打标记,查询时候一路记录比较,只在有必要时,也就是不得已要保证最大值的时候才下放标记。
- 维护最优势线段的好处在于每次只需要继续维护一半的区间,降低复杂度,也易于使得标记永久化
- 复杂度分析:维护线段时,把线段在线段树上分成最多$2logn$块,每块要分别处理,块内最多会下放至底层复杂度$O(logn)$,那么总复杂度$O(log^2n)$。很多时候跑不满。查询单点的时候,一路比较max去最优,复杂度$O(logn)$。
- 这题总的复杂度$O(nlog^2n)$。
code:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #define dbg(x) cerr << #x << " = " << x <<endl 7 #define dbg2(x,y) cerr << #x << " = " << x << " " << #y << " = " << y <<endl 8 using namespace std; 9 typedef long long ll; 10 typedef double dl; 11 typedef pair<int,int> pii; 12 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 13 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 14 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 15 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 16 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 17 template<typename T>inline T read(T&x){ 18 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 19 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 20 } 21 const int N=1e5+7; 22 const dl eps=1e-9; 23 int T,tot; 24 struct seg{ 25 dl k,b;int id; 26 seg(dl k=0,dl b=0,int id=0):k(k),b(b),id(id){} 27 }; 28 inline int chk(dl x,dl y){return x-y>=eps?1:(x-y<=-eps?-1:0);} 29 struct Lc_segment_tree{ 30 seg s[N<<2]; 31 #define lc i<<1 32 #define rc i<<1|1 33 inline void Pushdown(int i,int L,int R,seg c){ 34 if(!s[i].id){s[i]=c;return;} 35 int mid=L+R>>1; 36 dl l1=s[i].k*L+s[i].b,r1=s[i].k*R+s[i].b; 37 dl l2=c.k*L+c.b,r2=c.k*R+c.b; 38 if(~chk(l1,l2)&&~chk(r1,r2))return; 39 if(l2>l1&&r2>r1){s[i]=c;return;}//go back when arriving leaf 40 dl x=(s[i].b-c.b)/(c.k-s[i].k); 41 if(x<=mid){ 42 Pushdown(lc,L,mid,r1<r2?s[i]:c); 43 if(r1<r2)s[i]=c; 44 } 45 else{ 46 Pushdown(rc,mid+1,R,r1<r2?c:s[i]); 47 if(r1>r2)s[i]=c; 48 } 49 } 50 inline void Update(int i,int L,int R,int ql,int qr,seg c){ 51 if(ql<=L&&qr>=R){Pushdown(i,L,R,c);return;} 52 int mid=L+R>>1; 53 if(ql<=mid)Update(lc,L,mid,ql,qr,c); 54 if(qr>mid)Update(rc,mid+1,R,ql,qr,c); 55 } 56 inline void QMAX(seg&a,seg b,int x){ 57 dl ya=a.k*x+a.b,yb=b.k*x+b.b; 58 if(ya<yb||!chk(ya,yb)&&a.id>b.id)a=b; 59 } 60 inline seg Query_max(int i,int L,int R,int x){ 61 if(L==R)return s[i]; 62 int mid=L+R>>1;seg ret=s[i]; 63 if(x<=mid)QMAX(ret,Query_max(lc,L,mid,x),x); 64 else QMAX(ret,Query_max(rc,mid+1,R,x),x); 65 return ret; 66 } 67 }thxorz; 68 #define all 1,1,39989 69 int main(){//freopen("test.in","r",stdin);//freopen("test.ans","w",stdout); 70 read(T); 71 for(register int i=1,opt,x1,x2,y1,y2,las=0;i<=T;++i){ 72 read(opt);dl k; 73 if(opt){ 74 read(x1),read(y1),read(x2),read(y2); 75 x1=(x1+las-1)%39989+1,y1=(y1+las-1)%1000000000+1; 76 x2=(x2+las-1)%39989+1,y2=(y2+las-1)%1000000000+1;//mistake. 77 if(x1>x2)swap(x1,x2),swap(y1,y2); 78 if(x1==x2)thxorz.Update(all,x1,x2,seg(0,_max(y1,y2),++tot)); 79 else k=(dl)(y2-y1)/(x2-x1),thxorz.Update(all,x1,x2,seg(k,y1-k*x1,++tot)); 80 } 81 else{ 82 read(x1);x1=(x1+las-1)%39989+1; 83 printf("%d\n",las=thxorz.Query_max(all,x1).id); 84 } 85 } 86 return 0; 87 }
拓展
那么现在看来,李超线段树也就不难理解了。于是有一些魔改。
BZOJ1568 [JSOI2008]Blue Mary开公司
这题是一个直线修改,也就是直接从线段树根节点就开始下放了。复杂度$O(nlogn)$。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #define dbg(x) cerr << #x << " = " << x <<endl 7 using namespace std; 8 typedef long long ll; 9 typedef double db; 10 typedef pair<int,int> pii; 11 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 12 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 13 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 14 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 15 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 16 template<typename T>inline T read(T&x){ 17 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 18 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 19 } 20 const int N=50000+7; 21 const db eps=1e-9; 22 struct seg{ 23 db k,b;int vis; 24 seg(db k=0,db b=0,int vis=0):k(k),b(b),vis(vis){} 25 }; 26 inline int chk(db x,db y){return x-y>=eps?1:(x-y<=-eps?-1:0);} 27 struct lc_segment_tree{ 28 seg s[N<<2]; 29 #define lc i<<1 30 #define rc i<<1|1 31 inline void Pushdown(int i,int L,int R,seg c){ 32 if(!s[i].vis){s[i]=c;return;} 33 db l1=s[i].k*L+s[i].b,r1=s[i].k*R+s[i].b; 34 db l2=c.k*L+c.b,r2=c.k*R+c.b; 35 if(~chk(l1,l2)&&~chk(r1,r2))return; 36 if(l1<l2&&r1<r2){s[i]=c;return;} 37 int mid=L+R>>1;db x=(c.b-s[i].b)/(s[i].k-c.k); 38 if(x<=mid){ 39 Pushdown(lc,L,mid,r1<r2?s[i]:c); 40 if(r1<r2)s[i]=c; 41 } 42 else{ 43 Pushdown(rc,mid+1,R,r1>r2?s[i]:c); 44 if(r1>r2)s[i]=c; 45 } 46 } 47 inline void Update(int i,int L,int R,int ql,int qr,seg c){ 48 if(ql<=L&&qr>=R){Pushdown(i,L,R,c);return;} 49 int mid=L+R>>1; 50 if(ql<=mid)Update(lc,L,mid,ql,qr,c); 51 if(qr>mid)Update(rc,mid+1,R,ql,qr,c); 52 } 53 inline db Query_max(int i,int L,int R,int x){ 54 if(L==R)return s[i].k*x+s[i].b; 55 int mid=L+R>>1;db ret=_max(0.0,s[i].k*x+s[i].b);//注意无线段保存时的正确性qwq但这里不需要 56 if(x<=mid)MAX(ret,Query_max(lc,L,mid,x)); 57 else MAX(ret,Query_max(rc,mid+1,R,x)); 58 return ret; 59 } 60 }stothx; 61 int T,x; 62 char s[14]; 63 db k,b; 64 #define all 1,1,50000 65 int main(){//freopen("test.in","r",stdin);//freopen("test.ans","w",stdout); 66 read(T);while(T--){ 67 scanf("%s",s); 68 if(s[0]=='P')scanf("%lf%lf",&b,&k),stothx.Update(all,1,50000,seg(k,b-k,1)); 69 else read(x),printf("%d\n",(int)stothx.Query_max(all,x)/100); 70 // else read(x),printf("%f\n",stothx.Query_max(all,x)); 71 } 72 return 0; 73 }
如果把查询范围改到$1e9$呢?两种方法。
- 离散化询问坐标,然后在离散化坐标上建成线段树,这时注意一下区间上的$L,R$和原来概念就不一样惹。
- 动态开点。没写。
BZOJ4515 [SDOI2016]游戏
当把问题放到树上,就非常毒瘤了。。这题我写了1h,调代码调了2~3h。
问题是从带边权的树上一个起点到终点路上每个节点和$a*dis+b$($a,b$给定,$dis$是距离起点的距离)取最小值。
- 最小基本就是修改一下,但是注意一下对应节点上不存在的话要返回INF。
- 树上问题采用树剖分成若干条链,然后一条链对应了dfs序连续一段区间。
- 然后这个$a*dis+b$和李超线段树维护内容相似,考虑怎么维护每一条重链。
- 相当于在线段树上一段连续区间上插入线段。但是,这个区间的每个点横坐标不固定唉。。因为每次修改的$dis$是变的。
- 考虑让他固定死。对于上行段, $a*dis+b=a(depth_s-depth_i)+b=-a*depth_i+a*depth_s+b$,成为了一个标准的斜截式。
- 下行段 $a*dis+b=a(depth_s+depth_i-2depth_{lca})+b=a*depth_i+a*depth_s-2a*depth_{lca}+b$。
- 分上下行维护就好了。
- 这告诉我们李超线段树注重的是整块的区间,只要添加线段的区间内的横坐标是递增的,维护就没问题。
- 还有一个问题:区间(树链)查询最小值。
- 发现这个可以对每个区间开一个$minv$维护,表示维护这个区间子树内所有点在所有线段上出现的最小值。也就是说,为了区间查询在$O(logn)$级别,我们在递归到整块区间的时候必须直接返回这个$minv$保证复杂度。
- 那么,在pushdown操作中,要及时维护这个信息,在update函数中也要不断pushup。(具体看code)
- 这样查询时一路计算,到整块直接返回,保证复杂度。
- 时间总复杂度$O(nlog^3n)$。因为树剖和李超线段树常数都比较小。。所以可以过。
我写的时候犯了很多错误,再一次反应了自己基本功不扎实。。。
特别注意一个query操作,传参$ql和qr$不是正常线段树的传法!为了一路计算比较,必须修改部分。同时树剖也有部分魔改。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #define dbg(x) cout << #x << " = " << x <<endl 7 using namespace std; 8 typedef long long ll; 9 typedef double db; 10 typedef pair<int,int> pii; 11 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 12 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 13 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 14 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 15 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 16 template<typename T>inline T read(T&x){ 17 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 18 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 19 } 20 const int N=1e5+7; 21 const ll INF=123456789123456789; 22 struct tree{int to,nxt,w;}G[N<<1]; 23 int Head[N],tot; 24 int n,q; 25 inline void Addedge(int x,int y,int z){ 26 G[++tot].to=y,G[tot].nxt=Head[x],Head[x]=tot,G[tot].w=z; 27 G[++tot].to=x,G[tot].nxt=Head[y],Head[y]=tot,G[tot].w=z; 28 } 29 #define y G[j].to 30 ll dep[N]; 31 int st[N],pos[N],tim,son[N],fa[N],topfa[N],fl[N],cnt[N]; 32 void dfs1(int x,int f){ 33 fa[x]=f;int tmp=-1;cnt[x]=1;fl[x]=fl[f]+1; 34 for(register int j=Head[x];j;j=G[j].nxt)if(y^f) 35 dep[y]=dep[x]+G[j].w,dfs1(y,x),cnt[x]+=cnt[y],MAX(tmp,cnt[y])&&(son[x]=y); 36 }//mistake line 36:counting sons/difference between floor and depth 37 void dfs2(int x,int topf){ 38 topfa[x]=topf,st[x]=++tim,pos[tim]=x;if(!son[x])return;dfs2(son[x],topf); 39 for(register int j=Head[x];j;j=G[j].nxt)if((y!=fa[x])&&(y!=son[x]))dfs2(y,y); 40 }//mistake line39 Head[x] 41 #undef y 42 struct seg{ 43 ll k,b;int vis; 44 seg(ll k=0,ll b=0,int vis=0):k(k),b(b),vis(vis){} 45 }; 46 struct lc_segment_tree{ 47 seg s[N<<2];ll minv[N<<2]; 48 #define lc i<<1 49 #define rc i<<1|1 50 #define lb dep[pos[L]] 51 #define rb dep[pos[R]] 52 #define calc(c,x) (c.k*x+c.b) 53 inline void Pushup(int i){MIN(minv[i],_min(minv[lc],minv[rc]));} 54 void Pushdown(int i,int L,int R,seg c){//dbg(calc(c,lb)),dbg(calc(c,rb)); 55 if(!s[i].vis){s[i]=c;MIN(minv[i],_min(calc(c,lb),calc(c,rb)));return;} 56 ll l1=calc(s[i],lb),r1=calc(s[i],rb),l2=calc(c,lb),r2=calc(c,rb); 57 if(l1<=l2&&r1<=r2)return; 58 if(l1>l2&&r1>r2){s[i]=c;MIN(minv[i],_min(l2,r2));return;} 59 int mid=L+R>>1; 60 db x=(db)(c.b-s[i].b)/(s[i].k-c.k); 61 if(x<=dep[pos[mid]]){ 62 Pushdown(lc,L,mid,r1<r2?c:s[i]); 63 if(r1>r2)s[i]=c,MIN(minv[i],r2); 64 else MIN(minv[i],minv[lc]); 65 } 66 else{ 67 Pushdown(rc,mid+1,R,r1<r2?s[i]:c); 68 if(r1<r2)s[i]=c,MIN(minv[i],l2); 69 else MIN(minv[i],minv[rc]);//mistake:remember to pushup after pushdowning 70 } 71 } 72 void Update(int i,int L,int R,int ql,int qr,seg c){//dbg(L),dbg(R),dbg(ql),dbg(qr); 73 if(ql<=L&&qr>=R){Pushdown(i,L,R,c);return;} 74 int mid=L+R>>1; 75 if(ql<=mid)Update(lc,L,mid,ql,qr,c); 76 if(qr>mid)Update(rc,mid+1,R,ql,qr,c); 77 Pushup(i); 78 } 79 ll Query(int i,int L,int R,int ql,int qr){//dbg(L),dbg(R),dbg(ql),dbg(qr),dbg(minv[i]); 80 if(ql<=L&&qr>=R)return minv[i]; 81 int mid=L+R>>1; 82 ll ret=s[i].vis?_min(calc(s[i],dep[pos[ql]]),calc(s[i],dep[pos[qr]])):INF; 83 if(qr<=mid)MIN(ret,Query(lc,L,mid,ql,qr)); 84 else if(ql>mid)MIN(ret,Query(rc,mid+1,R,ql,qr)); 85 else MIN(ret,Query(lc,L,mid,ql,mid)),MIN(ret,Query(rc,mid+1,R,mid+1,qr));//mistake:distinguish ql,qr,L,R!线段树板子背的过于熟练于是疏忽了 86 return ret; 87 } 88 }T; 89 inline int LCA(int x,int y){ 90 while(topfa[x]^topfa[y])fl[topfa[x]]<fl[topfa[y]]?y=fa[topfa[y]]:x=fa[topfa[x]]; 91 return fl[x]<fl[y]?x:y; 92 } 93 inline void Modify(int s,int t,int a,int b){ 94 int lca=LCA(s,t),tmp=s; 95 ll K=-a,B=a*dep[s]+b; 96 while(topfa[s]^topfa[lca])T.Update(1,1,n,st[topfa[s]],st[s],seg(K,B,1)),s=fa[topfa[s]]; 97 T.Update(1,1,n,st[lca],st[s],seg(K,B,1)); 98 K=a,B=a*(dep[tmp]-(dep[lca]<<1))+b;//mistake:tmp and s! 99 while(topfa[t]^topfa[lca])T.Update(1,1,n,st[topfa[t]],st[t],seg(K,B,1)),t=fa[topfa[t]]; 100 T.Update(1,1,n,st[lca],st[t],seg(K,B,1)); 101 } 102 inline ll Query(int s,int t){ 103 ll ret=INF;ll tmp; 104 while(topfa[s]^topfa[t]){ 105 if(fl[topfa[s]]<fl[topfa[t]])s^=t^=s^=t; 106 MIN(ret,tmp=T.Query(1,1,n,st[topfa[s]],st[s])),s=fa[topfa[s]];//miskate:I've mixed the 'st' and 'id' up 107 } 108 MIN(ret,fl[s]<fl[t]?T.Query(1,1,n,st[s],st[t]):T.Query(1,1,n,st[t],st[s])); 109 return ret; 110 } 111 112 int main(){//freopen("8.in","r",stdin);freopen("8.ans","w",stdout); 113 read(n),read(q); 114 for(register int i=1,x,y,z;i<n;++i)read(x),read(y),read(z),Addedge(x,y,z); 115 dfs1(1,0),dfs2(1,1); 116 for(register int i=1;i<=n<<2;++i)T.minv[i]=INF; 117 for(register int i=1,opt,s,t,a,b;i<=q;++i){ 118 read(opt); 119 if(opt==1)read(s),read(t),read(a),read(b),Modify(s,t,a,b); 120 else read(s),read(t),printf("%lld\n",Query(s,t)); 121 } 122 return 0; 123 } 124 //AC:1/3
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /*********************************************** 2 line36:树剖好久不写,找重儿子都找错了。。 3 line31:注意fl(floor)和dep(depth)的区别,前面是用作树剖跳的,后者是算横坐标的。 4 line39:typo 5 line64,69:记得更新好minv 6 line85:重点错误,已解释 7 line98:s跳过之后就不是原来的s了。。 8 line106:typo 9 ***********************************************/丢人现眼