笛卡尔树及例题

笛卡尔树

选择数列里面最小的值作为根,从左边右边选择一个最小的值当做左右儿子,循环往复。拿最大值当做根也可以。主要取决于具体问题。

性质

1,区间最小(大)值。区间最小值就为区间左右端点值的LCA所代表的值。

就比如 3 5 1 7 4 6 2,那么5 6 之间的最小值就是5 与 6在树上的LCA,画个图可以看出是1.

2,中序遍历一遍就是原数组。

3,一个点的子树就是以这个点为最小值的极长的区间,因此一个数左边第一个比它小的数就是树上第一个左拐的位置。

构造

线段树构造的总复杂度是NlogN。每次查找最小值是logN。

ST表预处理NlognN。然后O(1)查找最小值。总复杂度是NlogN。

类单调栈方法总复杂度是N。

单调栈构造

特征:只有右半链会变,左半链一定不变。

int n;
int a[maxn],le[maxn],ri[maxn];
void build_dct(/* arguments */) {
    stack<int> st;
    int last=0,root=0;
    for(int i=1;i<=n;i++) {
        last=0;
        while (!st.empty() && a[st.top()]>a[i]) {
            last=st.top();
            st.pop();
        }
        if(!st.empty()) ri[st.top()]=i;
        else root=i;
        le[i]=last;
        st.push(i);
    }
    for(int i=1;i<=n;i++) {
        printf("%d %d %d %d\n",i,a[i],le[i],ri[i] );
    }
    /*  输入内容
        7
        3 5 1 7 4 6 2
        输出内容
        1 3 0 2
        2 5 0 0
        3 1 1 7
        4 7 0 0
        5 4 4 6
        6 6 0 0
        7 2 5 0
    */
}
void init() {
    n=rr();
    for(int i=1;i<=n;i++) {
        a[i]=rr();
    }
}
int main(int argc, char const *argv[]) {
    init();
    build_dct();
    return 0;
}

用处

求出所有区间的最小值。

序列结构转化成树形结构。

例题

给你一个1∼n的排列p1,p2,…,pn。

让你找到一个1∼n的排列q,满足对于任意区间[l,r](1≤l≤r≤n)满足pl,pl+1,…,pr中的最小值的位置,和ql,ql+1,…,qrr的最小值的位置相同。

输出满足条件的字典序最小的q。

输入格式

第一行一个整数n(1≤n≤106)。

接下来一行,一个长度为n的排列p1,p2,…,pn。

输出格式

一行nn个数q1,q2,…,qn,表示这个字典序最小的排列。

样例输入
5
4 3 1 5 2
样例输出
3 2 1 5 4
思路

样例图
由于笛卡尔树描述了序列数之间的关系,所以这里可以利用笛卡尔树。构出笛卡尔树之后,先序遍历一遍即可获得新的满足同样笛卡尔树关系的序列。

#include <bits/stdc++.h>
typedef long long ll;
#define pb push_back
#define pob pop_back
#define mem(a,b) memset(a,b,sizeof(a))
#define all(a) (a).begin(),(a).end()
#define debug(a) cout<<#a<<"="<<a<<endl;
inline ll rr(){ll f=1,x=0;char ch;do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');return f*x;}
using namespace std;
const ll INF=0x3f3f3f3f,inf=0x3f3f3f3f3f3f3f;
const int maxn=1e6+6;

int n,tot;
int a[maxn],le[maxn],ri[maxn],ans[maxn];
void dfs(int u) {
    if(!u) return ;
    ans[u]=++tot;
    dfs(le[u]);
    dfs(ri[u]);
}
void build_dct(/* arguments */) {
    stack<int> st;
    int last=0,root=0;
    for(int i=1;i<=n;i++) {
        last=0;
        while (!st.empty() && a[st.top()]>a[i]) {
            last=st.top();
            st.pop();
        }
        if(!st.empty()) ri[st.top()]=i;
        else root=i;
        le[i]=last;
        st.push(i);
    }
    dfs(root);
}
void init() {
    n=rr();
    for(int i=1;i<=n;i++) {
        a[i]=rr();
    }
}
int main(int argc, char const *argv[]) {
    init();
    build_dct();
    for(int i=1;i<=n;i++) {
        std::cout << ans[i] << ' ';
    }
    std::cout << '\n';
    return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值