【学习笔记】析合树
定义
为了方便,下面的定义都是对一个\(n\)阶排列
-
定义一个段的值域$ran[l,r] = [min \ a_i \ , \ max \ a_i] (l \le i \le r) $ ,对于一个排列\(ran[l,r] \ge r-l+1\)
定义1 一个段是连续的,当且\(|ran[l,r]|= r-l+1\) ,显然$ \varnothing ,[1,n],[i,i]$都是连续的
性质1:
对于两个连续段,它们的交一定是一个连续段
对于两个有交的连续段,它们的并一定是一个连续段
对于有交的\(A,B\),就只需要考虑 $ A\cap B , A \cup B $了,所以这意味着啥?
-
定义2 一个连续段是本原连续段,当且仅当满足不存在和它相交却没有包含关系的连续段
这样的连续段可以看成一个节点个数\(O(n)\)的树
-
定义3 定义上面由本原连续段形成的树为排列的析合树
性质2 :
析合树的节点的儿子一定满足下列性质之一:
将儿子节点连续段看成一个整体,形成了儿子序列,选一段是儿子区间
1.不存在任何一个儿子节点非平凡区间组成连续段,称为析点
2.任何儿子节点组成的区间都是连续段,称为合点
证明:
对于一个非平凡(不是所有或单个的儿子区间)是连续段,它不是本原的,那么一定存在另外一个连续段和它相交,注意到性质1中相交之后的三个部分是相对有序的,再考虑它们的并,这样下去一定可以得到完全覆盖整个儿子区间的很多个区间,它们是相对有序的,如果这些小区间不是平凡的,再进行这样的操作,最后得到儿子区间都是相对有序的,所以是满足非析即和性质的性质3 :
析合树可以构成所有连续段,且所有连续段在析合树上一定是以下两种形式:
1.树节点构成的连续段
2.合点的非平凡儿子区间构成的连续段
证明:
根据性质1,所有不可拆分的连续段一定都在树节点上,考虑一个连续段由\(v_1,v_2,\cdots,v_k(k>1)\)构成且,那找\(v_1\)到\(v_k\)的lca,由于v1到vk是连续拼接的并且lca的亲生儿子全部是本原的,所以一定可以用lca的直接儿子去代替这个区间
构造
-
基本方法:
考虑增量,假设做到i,已经得到了\(1->i-1\)位的析合森林,用一个栈维护这个析合森林
用下面的方法进行增量:
设置一个当前点\(now\),初始为\([i,i]\),考虑栈顶的点为top
1.如果top是合点,并且now可以作为它的最后一个儿子,那么将now设置为top的最后一个儿子,弹出top为新的now,重复这个过程
2.否则从top开始往回找到最少的栈顶的若干个点可以和now组成连续段,弹出他们,新加一个点向这些点和原来的now连边,将now设置为新点,重复这个过程
直到栈为空或2找不到
说明://这里仔细想想好像有点不大严谨所以改成说明了。。。
已经出栈的点无论如何增量一定是本原的,首先它现在一定是本原的
把和以后它相交的连续段与它的父亲取个交一定可以得到现在就和它相交的段,即它现在就不本原了
新加入连续段,需要考虑的本原连续段一定是和栈顶的一部分森林形成的
尝试归纳now一定是\([1,i]\)的本原连续段,显然\([i,i]\)是的,设下次的now为now'
如果now‘不本原一定存在什么和它相交,考虑它的r可能在哪里
由于now是本原所以,这个连续段的r一定不会在now
这样一定是增量之前已经存在的连续段显然和now'不会相交
-
复杂度分析:
1是\(O(n)\)的,如果在2中被合并掉的点是\(O(n)\)的,但是如果到最后都无法合并,复杂度可能会退化到\(O(n^2)\),所以需要一个\(L_i\)表示以\(i\)为右端点的连续段的最远左端点,超过了就break就\(O(n)\)了 -
如何求\(L\) :
-
线段树对r动态维护\(max(l,r)-min(l,r)-(r-l)\)取最小值0最前面的l即可
-
max和min可以利用单调栈维护
LCA ppt里的O(n)做法感觉除了建树以外用处不是很大,所以没有写
例题
-
n阶排列,询问包含\([l_i,r_i]\)的最小连续段
-
建出析合树之后根据性质 3 找到lca,如果它是析点那么答案就是它的区间,否则是l,r方向的直接儿子形成的儿子区间
-
code
#include<bits/stdc++.h> #define il inline #define rg register using namespace std; const int N=200010; int n,m,a[N],st1[N],st2[N],tp1,tp2,rt,L[N],R[N],M[N],id[N],cnt,typ[N],bin[20],st[N],tp; il int min(int x,int y){if(x<y)return x;return y;} il int max(int x,int y){if(x>y)return x;return y;} char gc(){ static char*p1,*p2,s[1000000]; if(p1==p2)p2=(p1=s)+fread(s,1,1000000,stdin); return(p1==p2)?EOF:*p1++; } int rd(){ int x=0;char c=gc(); while(c<'0'||c>'9')c=gc(); while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=gc(); return x; } char ps[1000000],*pp=ps; void flush(){fwrite(ps,1,pp-ps,stdout);pp=ps;} void push(char x){if(pp==ps+1000000)flush();*pp++=x;} void write(int l,int r){ static int sta[N],top; if(!l)push('0');else{ while(l)sta[++top]=l%10,l/=10; while(top)push(sta[top--]^'0');} push(' '); if(!r)push('0');else{ while(r)sta[++top]=r%10,r/=10; while(top)push(sta[top--]^'0');} push('\n'); } struct RMQ{ int lg[N],mn[N][17],mx[N][17]; void chkmn(int&x,int y){if(x>y)x=y;} void chkmx(int&x,int y){if(x<y)x=y;} void build(){ for(int i=bin[0]=1;i<20;++i)bin[i]=bin[i-1]<<1; for(int i=2;i<=n;++i)lg[i]=lg[i>>1]+1; for(int i=1;i<=n;++i)mn[i][0]=mx[i][0]=a[i]; for(int i=1;i<17;++i) for(int j=1;j+bin[i]-1<=n;++j) mn[j][i]=min(mn[j][i-1],mn[j+bin[i-1]][i-1]), mx[j][i]=max(mx[j][i-1],mx[j+bin[i-1]][i-1]); } int ask_mn(int l,int r){ int t=lg[r-l+1]; return min(mn[l][t],mn[r-bin[t]+1][t]); } int ask_mx(int l,int r){ int t=lg[r-l+1]; return max(mx[l][t],mx[r-bin[t]+1][t]); } }D; //维护L_i struct SEG{ #define ls (k<<1) #define rs (k<<1|1) int mn[N<<1],ly[N<<1]; void pushup(int k){mn[k]=min(mn[ls],mn[rs]);} void mfy(int k,int v){mn[k]+=v,ly[k]+=v;} void pushdown(int k){if(ly[k])mfy(ls,ly[k]),mfy(rs,ly[k]),ly[k]=0;} void update(int k,int l,int r,int x,int y,int v){ if(l==x&&r==y){mfy(k,v);return;} pushdown(k); int mid=(l+r)>>1; if(y<=mid)update(ls,l,mid,x,y,v); else if(x>mid)update(rs,mid+1,r,x,y,v); else update(ls,l,mid,x,mid,v),update(rs,mid+1,r,mid+1,y,v); pushup(k); } int query(int k,int l,int r){ if(l==r)return l; pushdown(k); int mid=(l+r)>>1; if(!mn[ls])return query(ls,l,mid); else return query(rs,mid+1,r); } }T; int o=1,hd[N],dep[N],fa[N][18]; struct Edge{int v,nt;}E[N<<1]; void add(int u,int v){ E[o]=(Edge){v,hd[u]};hd[u]=o++; // printf("%d %d\n",u,v); } void dfs(int u){ for(int i=1;bin[i]<=dep[u];++i)fa[u][i]=fa[fa[u][i-1]][i-1]; for(int i=hd[u];i;i=E[i].nt){ int v=E[i].v; dep[v]=dep[u]+1; fa[v][0]=u; dfs(v); } } int go(int u,int d){for(int i=0;i<18&&d;++i)if(bin[i]&d)d^=bin[i],u=fa[u][i];return u;} int lca(int u,int v){ if(dep[u]<dep[v])swap(u,v); u=go(u,dep[u]-dep[v]); if(u==v)return u; for(int i=17;~i;--i)if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i]; return fa[u][0]; } bool judge(int l,int r){return D.ask_mx(l,r)-D.ask_mn(l,r)==r-l;} //建树 void build(){ for(int i=1;i<=n;++i){ //单调栈 while(tp1&&a[i]<=a[st1[tp1]]) T.update(1,1,n,st1[tp1-1]+1,st1[tp1],a[st1[tp1]]),tp1--; while(tp2&&a[i]>=a[st2[tp2]]) T.update(1,1,n,st2[tp2-1]+1,st2[tp2],-a[st2[tp2]]),tp2--; T.update(1,1,n,st1[tp1]+1,i,-a[i]);st1[++tp1]=i; T.update(1,1,n,st2[tp2]+1,i,a[i]);st2[++tp2]=i; id[i]=++cnt;L[cnt]=R[cnt]=i; int le=T.query(1,1,n),now=cnt; while(tp&&L[st[tp]]>=le){ if(typ[st[tp]]&&judge(M[st[tp]],i)){ R[st[tp]]=i; add(st[tp],now); now=st[tp--]; }else if(judge(L[st[tp]],i)){ typ[++cnt]=1;//合点一定是被这样建出来的 L[cnt]=L[st[tp]];R[cnt]=i;M[cnt]=L[now]; add(cnt,st[tp--]);add(cnt,now); now=cnt; }else{ add(++cnt,now); do add(cnt,st[tp--]);while(tp&&!judge(L[st[tp]],i)); L[cnt]=L[st[tp]];R[cnt]=i;add(cnt,st[tp--]); now=cnt; } } st[++tp]=now; T.update(1,1,n,1,i,-1); } rt=st[1]; } void query(int r,int l){ int x=id[l],y=id[r]; int z=lca(x,y); if(typ[z]&1) l=L[go(x,dep[x]-dep[z]-1)], r=R[go(y,dep[y]-dep[z]-1)]; else l=L[z],r=R[z]; write(l,r); }//分lca为析或和,这里把叶子看成析的 int main(){ freopen("c.in","r",stdin); freopen("c.out","w",stdout); n=rd();for(int i=1;i<=n;++i)a[i]=rd(); D.build(); build(); dfs(rt); m=rd();for(int i=1;i<=m;++i)query(rd(),rd()); return flush(),0; } //20190612 //析合树