析合树
Posted ljzalc1022
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了析合树相关的知识,希望对你有一定的参考价值。
析合树
https://www.cnblogs.com/Paul-Guderian/p/11020708.html
定义
对于一个排列,称其中一个区间([l,r])为连续段若(r-l=max{[l,r]}-min{[l,r]}),即其中元素排序后权值形成一段连续的区间.
对于两个相交的连续段,发现它们的并是连续段,它们的交也是连续段.
所以我们可以找出(O(n))个本源连续段,满足其他所有连续段和本源连续段只有包含关系,且所有连续段都可以被若干本源连续段的并表示.
这样一来,本源连续段的包含关系就形成了一个树的结构,称其为析合树.
其中每个节点是析点或合点,满足
- 叶子是析点
- 析点的所有非平凡儿子区间(大小不等于1,也不是全集)都不是连续段
- 合点的所有非平凡儿子区间都是连续段
如此一来,所有连续段要么是树上的节点,要么是合点的非平凡儿子区间.
许多关于连续段的问题都可以在析合树上解决.
构造
考虑增量构造,从小到大枚举(r),并处理所有右端点为(r)的节点.
考虑维护一个栈,按栈顶向下从右到左的顺序储存所有还没有父亲的节点.
定义当前节点为(now),初始(now=[r,r]).并对栈顶元素重复以下判断.
- 若栈顶元素是合点,则判断(now)是否能成为它的最后一个儿子(对于合点在建立时纪录它第二个儿子的左端点),若能,则令它为(now),右端点变为(r),弹出栈顶
- 否则,若(now)和栈顶元素可以构造一个连续段,建立一个新的合点作为它们的父亲,令这个新建的点为(now),弹出栈顶
- 否则,看是否可以找出栈顶向下的一段连续元素,使得它们共同构成了一个连续段,建立一个新的析点作为他们的父亲,令这个新建的点为(now),弹出这些元素.若不能则退出
分析复杂度,前两种情况都是(O(1))的,第3种情况假如成功,那么复杂度均摊是(O(n))的,但是若失败可能会退化为(O(n^2))
所以可以先求出(L_i)表示以(i)为右端点的最大的连续段的左端点,可以解(L_i)直接判断是否可以成功找到这样的连续段.
那么如何求(L_i)呢,我们可以在(r)增大时对每个(l)维护(max{[l,r]}-min{[l,r]}-(r-l)),则最小值即=0的位置即是连续段的左端点,找到最左边的0就是(L_i)了.(max,min)的部分用单调栈维护即可.
建树部分复杂度(O(n)),求(L_i)复杂度(O(nlog n)),总时间复杂度(O(n log n))
Code
https://www.luogu.com.cn/problem/P4747
求包含([l,r])的最小连续段.
找到([l,l],[r,r])在析合树上的lca,若其为析点,那么答案为lca,若其为合点,那么答案是它们对应儿子构成的区间.
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <vector>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
using namespace std;
inline char gc() {
// return getchar();
static char buf[100000],*l=buf,*r=buf;
return l==r&&(r=(l=buf)+fread(buf,1,100000,stdin),l==r)?EOF:*l++;
}
template<class T> void rd(T &x) {
x=0; int f=1,ch=gc();
while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=gc();}
while(ch>=‘0‘&&ch<=‘9‘){x=x*10-‘0‘+ch;ch=gc();}
x*=f;
}
const int maxn=100000+50;
int n,m,a[maxn];
namespace rmq {
int bit[25],lg2[maxn];
int mn[25][maxn],mx[25][maxn];
void init() {
bit[0]=1;
for(int i=1;i<25;++i) bit[i]=bit[i-1]<<1;
lg2[0]=-1;
for(int i=1;i<=n;++i) lg2[i]=lg2[i>>1]+1;
for(int i=1;i<=n;++i) mn[0][i]=mx[0][i]=a[i];
for(int k=1;bit[k]<=n;++k) {
for(int i=1;i+bit[k]-1<=n;++i) {
mn[k][i]=min(mn[k-1][i],mn[k-1][i+bit[k-1]]);
mx[k][i]=max(mx[k-1][i],mx[k-1][i+bit[k-1]]);
}
}
}
inline int Qmax(int l,int r) {
int k=lg2[r-l+1];
return max(mx[k][l],mx[k][r-bit[k]+1]);
}
inline int Qmin(int l,int r) {
int k=lg2[r-l+1];
return min(mn[k][l],mn[k][r-bit[k]+1]);
}
}
namespace seg {
const int maxnode=maxn<<2;
int mn[maxnode],tag[maxnode];
inline void change(int u,int d) {
mn[u]+=d,tag[u]+=d;
}
inline void pushdown(int u) {
if(tag[u]) {
change(u<<1,tag[u]);
change(u<<1|1,tag[u]);
tag[u]=0;
}
}
inline void pushup(int u) {
mn[u]=min(mn[u<<1],mn[u<<1|1]);
}
void build(int u,int l,int r) {
tag[u]=0;
if(l==r) {
mn[u]=0;
return;
}
int mid=(l+r)>>1;
build(lson);
build(rson);
pushup(u);
}
void update(int u,int l,int r,int ql,int qr,int qv) {
if(l==ql&&r==qr) {
change(u,qv);
return;
}
int mid=(l+r)>>1;
pushdown(u);
if(qr<=mid) update(lson,ql,qr,qv);
else if(ql>mid) update(rson,ql,qr,qv);
else {
update(lson,ql,mid,qv);
update(rson,mid+1,qr,qv);
}
pushup(u);
}
int query(int u,int l,int r) {
if(l==r) return l;
int mid=(l+r)>>1;
pushdown(u);
if(mn[u<<1]==0) return query(lson);
else return query(rson);
}
}
namespace dct {
const int maxnode=maxn<<1;
int ncnt;
int root;
int rnk[maxn];
int L[maxnode],R[maxnode],M[maxnode],typ[maxnode];
int head[maxnode];
int dep[maxnode],parent[maxnode][20];
struct edge {
int to,nex;
edge(int to=0,int nex=0):to(to),nex(nex){}
};
vector<edge> G;
inline void addedge(int u,int v) {
G.push_back(edge(v,head[u])),head[u]=G.size()-1;
}
void dfs(int u) {
for(int i=1;i<20;++i) parent[u][i]=parent[parent[u][i-1]][i-1];
for(int i=head[u];~i;i=G[i].nex) {
int v=G[i].to;
dep[v]=dep[u]+1;
parent[v][0]=u;
dfs(v);
}
}
int jump(int u,int k) {
for(int i=19;~i;--i) if(k>>i&1) {
u=parent[u][i];
}
return u;
}
int lca(int u,int v) {
if(dep[u]>dep[v]) swap(u,v);
v=jump(v,dep[v]-dep[u]);
if(u==v) return u;
for(int i=19;~i;--i) {
if(parent[u][i]!=parent[v][i]) {
u=parent[u][i];
v=parent[v][i];
}
}
return parent[u][0];
}
inline bool judge(int l,int r) {return r-l==rmq::Qmax(l,r)-rmq::Qmin(l,r);}
void build() {
static int sta[maxnode],sta1[maxn],sta2[maxn]; int top=0,top1=0,top2=0;
seg::build(1,1,n);
memset(head,-1,sizeof(head));
for(int r=1;r<=n;++r) {
while(top1&&a[sta1[top1]]<a[r]) {
seg::update(1,1,n,sta1[top1-1]+1,sta1[top1],-a[sta1[top1]]);
--top1;
}
sta1[++top1]=r;
seg::update(1,1,n,sta1[top1-1]+1,sta1[top1],a[sta1[top1]]);
while(top2&&a[sta2[top2]]>a[r]) {
seg::update(1,1,n,sta2[top2-1]+1,sta2[top2],a[sta2[top2]]);
--top2;
}
sta2[++top2]=r;
seg::update(1,1,n,sta2[top2-1]+1,sta2[top2],-a[sta2[top2]]);
int u=++ncnt; L[u]=R[u]=r; rnk[r]=u;
int mnl=seg::query(1,1,n);
while(top&&L[sta[top]]>=mnl) {
if(typ[sta[top]]&&judge(M[sta[top]],r)) {
addedge(sta[top],u);
R[sta[top]]=r;
u=sta[top--];
}
else if(judge(L[sta[top]],r)) {
int v=++ncnt; typ[v]=1;
L[v]=L[sta[top]],R[v]=r,M[v]=L[u];
addedge(v,sta[top--]),addedge(v,u);
u=v;
}
else {
int v=++ncnt; addedge(v,u);
do addedge(v,sta[top--]); while(!judge(L[sta[top]],r));
L[v]=L[sta[top]],R[v]=r;
addedge(v,sta[top--]);
u=v;
}
}
sta[++top]=u;
seg::update(1,1,n,1,r,-1);
}
root=sta[top];
dfs(root);
}
inline void sol(int l,int r) {
l=rnk[l],r=rnk[r];
int w=lca(l,r);
if(!typ[w]) printf("%d %d
",L[w],R[w]);
else {
l=jump(l,dep[l]-dep[w]-1);
r=jump(r,dep[r]-dep[w]-1);
printf("%d %d
",L[l],R[r]);
}
}
}
int main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
rd(n);
for(int i=1;i<=n;++i) rd(a[i]);
rmq::init();
dct::build();
rd(m);
for(int i=1;i<=m;++i) {
int x,y; rd(x),rd(y);
dct::sol(x,y);
}
return 0;
}
以上是关于析合树的主要内容,如果未能解决你的问题,请参考以下文章
P4747 [CERC2017]Intrinsic Interval