笛卡尔树

堆+BST

性质

  1. 笛卡尔树的子树是对应序列上一段连续的区间
    考虑 BST,最初 \([1,n]\),考虑划分开 rt,分割成左右 2 个区间,且两个区间对应 2 棵子树,分治下去得证。

构造

考虑对于一个新节点 \(a_i\) 的加入势必只会影响当前树的最右链,因为要满足下标 BST 性质。

然后既然已经满足了 BST 性质,不妨先假设我们维护的是个小根堆,即点的权值小于等于其子树内的所有权值。

考虑当前右链满足自父亲到儿子权值单增,那么显然你 \(a\) 的插入本质上就是抽离出来 \(\ge a_i\) 的底部链,并将这条链接到 \(a\) 的左儿子,这条链自身的结构并不需要改变,然后再将 \(a_i\) 接到抽完的链的底部,作为其右儿子。

这个构造过程是很自然的,但为啥把这条链直接抽,并不需要改变其形态就是对的呢?

考虑这条链所挂着的其他儿子。

  • 堆性质,由于其最上的点满足 \(\ge a_i\),所以其子树也一定满足。

  • BST,显然,你各个点的子树都是 BST,自然不需要更改。

https://acm.hdu.edu.cn/showproblem.php?pid=1506

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int N=(int)(1e5+5);
vector<int>g[N];
int n,a[N],sz[N],ls[N],rs[N],ans,tp,st[N],du[N];

void dfs(int x) {
    sz[x]=1;
    for(int y:g[x]) {
        dfs(y); sz[x]+=sz[y];
    }
    ans=max(ans,sz[x]*a[x]);
}

void sol() {
    ans=-(int)(2e18);
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        ls[i]=rs[i]=du[i]=0;
        g[i].clear();
    }
    tp=0;
    for(int i=1;i<=n;i++) {
        while(tp&&a[st[tp]]>a[i]) {
            ls[i]=st[tp]; --tp;
        }
        if(tp) rs[st[tp]]=i;
        st[++tp]=i; 
    }
    for(int i=1;i<=n;i++) {
        if(ls[i]) g[i].pb(ls[i]),++du[ls[i]];
        if(rs[i]) g[i].pb(rs[i]),++du[rs[i]];
    }
    int rt=0;
    for(int i=1;i<=n;i++) {
        if(!du[i]) {
            rt=i; break ;
        }
    }
    dfs(rt);
    cout<<ans<<'\n';
}

signed main() {
    cin.tie(0); ios::sync_with_stdio(false);
    while(1) {
        cin>>n;
        if(!n) return 0;
        sol();
    }
    return 0;
}
posted @ 2022-11-12 12:51  FxorG  阅读(34)  评论(0编辑  收藏  举报