笛卡尔树

\(\text{Part 1.}\) 笛卡尔树是什么和其构建方法

笛卡尔树是一个二叉树,对于这棵树上的每一个节点,其都有两个值 \((k,w)\),其中 \(k\) 满足二叉搜索树的性质,\(w\) 满足堆的性质。如果所有的 \(k\) 和所有的 \(w\) 均不相同,那么笛卡尔树就是唯一的。

下面就是一个笛卡尔树的例子:

其中对于节点 \(i\),他的两个键值就是 \((i,a_i)\)。而如果在笛卡尔树中 \(w\) 满足小根堆的性质,那么我们称这棵笛卡尔树为小根笛卡尔树。

那么他是如何构建的呢?现在考虑对于一个序列 \(a\) 建笛卡尔树。

由于下下标肯定是单增的,所以新插入的点只能是已前面点的右儿子、前面点只能是它的左儿子。

所以此时考虑使用单调栈维护一个权值单调递增的下标序列。那么在插入一个新点的时候,这个点就是第一个比它小的点的右儿子。比这个新点大的最后一个点作为这个新点的左儿子。

code:

void build()
{
    for(int i = 1;i <= n;i++)
    {
        int pos = 0;
        while(top && a[stk[top]] > a[i])pos = stk[top],top--;
        if(top)ch[stk[top]][1] = i;
        ch[i][0] = pos;stk[++top] = i;
    }
}

注意笛卡尔树的模板题卡常。

\(\text{Part2.}\) 笛卡尔树的优美性质

我们这里一小根堆为例。

  1. \(u\) 的一个子树是一个连续的区间,并且一定是一个极长的区间。即不能向外扩展。

  2. \(u\) 的左右子树当中分别选出两个点 \(l,r\),区间 \([l,r]\) 的最小值在点 \(u\)

  3. 给定区间 \([l,r]\),区间 \([l,r]\) 的最小值在 \(\operatorname{lca}(l,r)\)

  4. Treap 是一颗笛卡尔树。那么我们就可以考虑去维护动态笛卡尔树了。

\(\text{Part 3.}\) 例题

1.P8051

考虑维护一个大根的笛卡尔树。那么从一颗树跳到下一棵树的操作就类似于从一个点跳到他在笛卡尔树上的一个儿子。

那么对于一个节点 \(i\) 考虑去维护 \(dp_i\) 表示从节点 \(i\) 开始,能跳多少次即节点 \(i\) 向下的最长链长度。

那么我们直接从根节点开始开始 \(dp\),那么转移即为:

\[dp_u = \min(dp_{ls} - [a_{ls} = a_{u}],dp_{rs} - [a_{rs} = a_u]) + 1 \]

然后对于使用魔力,我们可以直接用双指针去找最高可以在那一棵树开始即可。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int maxn = 3e5 + 10;
int n,k,a[maxn],b[maxn],ch[maxn][2];
int stk[maxn],dp[maxn],top;
int maxx[maxn];
pair<int,int> p[maxn];
void build()
{
    for(int i = 1;i <= n;i++)
    {
        int pos = 0;
        while(top && a[stk[top]] <= a[i])pos = stk[top],top--;
        if(top)ch[stk[top]][1] = i;
        ch[i][0] = pos;stk[++top] = i;
    }
}

void turns(int u,int ls){dp[u] = max(dp[u],dp[ls] - (a[u] == a[ls]) + 1);}

void dfs(int u)
{
    if(ch[u][0])dfs(ch[u][0]),turns(u,ch[u][0]);
    if(ch[u][1])dfs(ch[u][1]),turns(u,ch[u][1]);
}

signed main()
{
    cin >> n >> k;
    for(int i = 1;i <= n;i++)cin >> a[i],p[i] = {a[i],i};
    for(int i = 1;i <= k;i++)cin >> b[i];
    build();dfs(stk[1]);sort(p + 1,p + n + 1);sort(b + 1,b + k + 1);
    int ans = dp[1];
    for(int i = 1;i <= n;i++)maxx[i] = max(maxx[i - 1],dp[p[i].second]);
    int j = 1;
    for(int i = 1;i <= n;i++)
    {
        while(p[i].first == p[i + 1].first)i++;
        while(b[j] < p[i + 1].first && j <= k)ans += maxx[i],j++;
    }
    while(j <= k)ans += maxx[n],j++;
    cout << ans;
    return 0;
}

posted @ 2025-07-27 14:53  sqrtqwq  阅读(20)  评论(0)    收藏  举报