Cartesian tree study notes

Sounds like a Dian powerful, practical and very clever

These two learned: ReMoon - monotonous stack of applications --- Cartesian tree with the virtual tree

       ACM algorithm Daily - Algorithm Collection | magic Cartesian tree - HDU 1506


 

Introduction ~ ~

Although the name with a "tree", but in fact Cartesian tree for a sequence of transformation and get more information on this sequence through this transformation

For a simple sequence: $ 2,8,5,7,1,4 $, we can establish the following Cartesian tree ($ pos $ represents the position of the original sequence, $ val $ represents the value of the position)

Cartesian tree has such basic properties:

   For the tree any point $ x $ and son about $ left, right $, are:

   1. $pos[left]<pos[x]<pos[right]$

   2. $val[x]<val[left],val[right]$

That explains the general said $ pos $ satisfy a binary search tree, $ val $ meet heap

 

Straight point of view, is that these two extension properties:

   At any point $ X $ tree subtree rooted configuration,

   1. Each node is continuous $ $ POS, POS and to $ $ preorder traversal of the sequence is the prosequence ($ a $ POS binary search tree satisfies available)

   Point 2. $ x $ $ $ Val whole subtree is the minimum ($ a $ Val satisfy stack available)


 

~ ~ Achievements

With the understanding of Cartesian tree structure, now consider how to establish tree

 

[Method] priority to meet the $ val $

To satisfy the condition $ val $ priority, it would be a contribution from the top down

Extending the use of the above two properties, each selected for the current segment $ [l, r] $ $ minimum value resides Val $ $ $ POS (referred $ pos = i $) as a sub-root of the tree

Then for $ [l, i-1], [+ 1, r] $ recursively repeating the above process

Wherein the selected minimum interval where $ Val $ $ $ POS segment tree may be used to optimize

The total complexity $ O (nlogn) $

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=100005;
const int INF=1<<30;

int n;
int val[N];

int sz;
int t[N<<2];

inline void Add(int i)
{
    int k=i+sz-1;
    t[k]=i;
    k>>=1;
    while(k)
    {
        int left=t[k<<1],right=t[k<<1|1];
        t[k]=(val[left]<val[right]?left:right);
        k>>=1;
    }
}

inline int Query(int k,int l,int r,int a,int b)
{
    if(a>r || b<l)
        return 0;
    if(a>=l && b<=r)
        return t[k];
    
    int mid=(a+b)>>1;
    int left=Query(k<<1,l,r,a,mid),right=Query(k<<1|1,l,r,mid+1,b);
    return (val[left]<val[right]?left:right);
}

void Init()
{
    sz=1;
    while(sz<n)
        sz<<=1;
    
    val[0]=INF;
    for(int i=1;i<(sz<<1);i++)
        t[i]=0;
    for(int i=1;i<=n;i++)
        Add(i);
}

int ls[N],rs[N];

inline int Build(int l,int r)
{
    if(l>r)
        return 0;
    
    int pos=Query(1,l,r,1,sz);
    ls[pos]=Build(l,pos-1);
    rs[pos]=Build(pos+1,r);
    return pos;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&val[i]);
    
    Init();
    int root=Build(1,n);
    
/*    for(int i=1;i<=n;i++)
        printf("i=%d: ls=%d rs=%d\n",i,ls[i],rs[i]);*/
    return 0;
}
View Code

 

Methods two] priority to meet the $ pos $

Since for the subtree preorder traversal of the original sequence order, so considered for $ i = 1 \ text {~} order n $ sequentially added node and adjust the structure of the tree, so that the sub-sequence current tree $ [1, i ] $ posed by Cartesian tree

Because $ POS $ meet binary sort tree, and $ I $ in the interval $ [1, i] $ of $ POS $ maximum, so $ I $ inserted location sequence $ [1, i-1] $ constituted Descartes root of the tree has to go right son, he went to the empty node until

After such insertion, $ i $ of $ pos $ already meet the requirements, but not necessarily meet the $ val $ heap

So consider how to adjust the current tree

If the $ I $ $ $ Val not meet the requirements, i.e., the presence of certain (s) ancestor J $ $, $ J $ such that the subtree rooted at the full $ $ Val greater than $ val [i] $; we need to adjust the apparent $ i $ a position that the $ i $ be the $ j $ ancestors

It is this operation:

   0. After just inserted completed, the tree may be so

    

   1. The $ I $ move upwardly layer; this case, since $ pos [k] <pos [i] $, $ k $ therefore be left son of $ I $, $ k '$ $ k $ still left son

   

   2. Continue the $ i $ move up a layer, like, $ j $ should belong to the $ i $ left subtree; might as well let $ j $ is $ i $ left his son, $ k $ to $ j $ of Right son (using this adjustment method, $ j, k, k ' $ mutually the same side of the original even )

   

In the above adjusting operation is $ [1, i-1] $ Cartesian tree sequence composed of the rightmost strand (i.e., the right son of the root node has been taking this path) of the

After the treatment, we compare the before and after adjustment tree, found only a few places has changed:

   1. $ k $ right son became empty node

   2. $ j $ father into a $ i $, and $ j $ is $ i $ left son

   3. $ i $ inherits the original $ j $ father

In fact, even $ i $ to $ j $ path very, very long, a total of only three places have changed, so we are not very complicated adjustments

The big question now becomes, how to find the $ j $

Look back to the rightmost chain, due to the $ val $ satisfy the heap, so each node $ val $ chain on the far right is monotonically increasing; may consider maintaining a monotonous stack, the stack is loaded with nodes on the rightmost chain $ pos $

And we are looking for $ j $, is $ val [j] <val [i] $, and the closest to the bottom of the stack elements

 

After understanding the principle, rearrange ideas, simple and clear as possible to build Cartesian tree:

   1. Maintenance of the rightmost stack monotone chain

   2. Each insert the current $ I $, continuously monotone pop stack in the stack until the stack satisfies $ FA $ $ val [fa] <val [i] $, is the last pop J $ $

   3. The $ i $ as the right of his son $ fa $, $ j $ a $ i $ left son

It is not very simple owo

 

复杂度$O(n)$,是相当优秀的一种方法

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

const int N=100005;

int n;
int a[N];

int root;
int ls[N],rs[N];
vector<int> v;

void Build()
{
    for(int i=1;i<=n;i++)
    {
        int j=0;
        while(v.size() && a[v.back()]>a[i])
        {
            j=v.back();
            v.pop_back();
        }
        
        if(!v.size())
            root=i;
        else
            rs[v.back()]=i;
        
        ls[i]=j;
        v.push_back(i);
    }
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    
    Build();
    
/*    for(int i=1;i<=n;i++)
        printf("i=%d ls=%d rs=%d\n",i,ls[i],rs[i]);*/
    return 0;
}
View Code

所以在一些情况下,笛卡尔树的题目可以不用建树,直接用单调栈就够了


 

~应用~

最简单的一个应用是求元素的左右延伸区间

具体点说,就是对于一个数列$a$,询问以$a[i]$为区间最大(小)值的最长区间

使用笛卡尔树,就可以通过$O(n)$的预处理做到$O(1)$查询:进行中序遍历,每个节点$x$的子树的$pos$最小、最大值就是答案

模板题:HDU 1506 ($Largest\ Rectangle\ in\ a\ Histogram$)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

typedef long long ll;
const int N=100005;

int n;
int a[N];

int root;
int ls[N],rs[N];
vector<int> v;

void Build()
{
    v.clear();
    memset(ls,0,sizeof(ls));
    memset(rs,0,sizeof(rs));
    
    for(int i=1;i<=n;i++)
    {
        int j=0;
        while(v.size() && a[v.back()]>a[i])
        {
            j=v.back();
            v.pop_back();
        }
        
        if(!v.size())
             root=i;
        else
            rs[v.back()]=i;
        
        ls[i]=j;
        v.push_back(i);
    }
}

int l[N],r[N];

void dfs(int x)
{
    l[x]=r[x]=x;
    
    if(ls[x])
    {
        dfs(ls[x]);
        l[x]=l[ls[x]];
    }
    if(rs[x])
    {
        dfs(rs[x]);
        r[x]=r[rs[x]];
    }
}

int main()
{
    scanf("%d",&n);
    while(n)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        
        Build();
        
        dfs(root);
        
        ll ans=0;
        for(int i=1;i<=n;i++)
            ans=max(ans,ll(a[i])*(r[i]-l[i]+1));
        printf("%lld\n",ans);
        
        scanf("%d",&n);
    }
    return 0;
}
View Code

 

一个稍微高级一点的应用,就是给出分治的边界

一道不错的题:Luogu P4755 ($Beautiful\ Pair$)

官方题解已经很完善了:FlierKing - 题解 P4755 【Beautiful Pair】

简单点说,就是每次取当前区间$[l,r]$的最大值$a_i$,那么$i$即为笛卡尔树中 此区间对应子树的根节点

于是将区间分成两部分$[l,i-1],[i+1,r]$的操作,就可以转化成笛卡尔树上的分治

同时,这个题解将“统计$[l,r]$中$a_i\leq x$的数量”这个主席树问题,离线后通过拆分转化为树状数组问题,设计十分巧妙

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
typedef pair<int,int> pii;
const int N=100005;

int n;
int a[N];

int root;
int ls[N],rs[N];
vector<int> v;

void Build()
{
    for(int i=1;i<=n;i++)
    {
         int j=0;
        while(v.size() && a[v.back()]<a[i])
        {
            j=v.back();
            v.pop_back();
        }
        
        if(!v.size())
            root=i;
        else
            rs[v.back()]=i;
        
        ls[i]=j;
        v.push_back(i);
    }
}

int l[N],r[N];

inline void dfs(int x)
{
    if(ls[x])
    {
        dfs(ls[x]);
        l[x]=l[ls[x]];
    }
    else
        l[x]=x;
    if(rs[x])
    {
        dfs(rs[x]);
        r[x]=r[rs[x]];
    }
    else
        r[x]=x;
}

vector<pii> add[N];

inline void Solve(int x)
{
    int lp=x-l[x],rp=r[x]-x;
    if(lp<rp)
        for(int i=l[x];i<=x;i++)
        {
            add[r[x]].push_back(pii(a[x]/a[i],1));
            add[x-1].push_back(pii(a[x]/a[i],-1));
        }
    else
        for(int i=x;i<=r[x];i++)
        {
            add[x].push_back(pii(a[x]/a[i],1));
            add[l[x]-1].push_back(pii(a[x]/a[i],-1));
        }
    
    if(ls[x])
        Solve(ls[x]);
    if(rs[x])
        Solve(rs[x]);
}

vector<int> pos;

int t[N];

inline int lowbit(int x)
{
    return x&(-x);
}

inline void Add(int k,int x)
{
    for(int i=k;i<=n;i+=lowbit(i))
        t[i]+=x;
}

inline int Query(int k)
{
    int res=0;
    for(int i=k;i;i-=lowbit(i))
        res+=t[i];
    return res;
}

int main()
{
    scanf("%d",&n);
    pos.push_back(0);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),pos.push_back(a[i]);
    
    sort(pos.begin(),pos.end());
    pos.resize(unique(pos.begin(),pos.end())-pos.begin());
    
    Build();
    dfs(root);
    
    Solve(root);
    
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        int p=lower_bound(pos.begin(),pos.end(),a[i])-pos.begin();
        Add(p,1);
        
        for(int j=0;j<add[i].size();j++)
        {
            int lim=lower_bound(pos.begin(),pos.end(),add[i][j].first)-pos.begin();
            if(pos[lim]>add[i][j].first)
                lim--;
            
            ans=ans+add[i][j].second*Query(lim);
        }
    }
    printf("%lld\n",ans);
    return 0;
}
View Code

 

 

(待续)

Guess you like

Origin www.cnblogs.com/LiuRunky/p/Cartesian_Tree.html