笛卡尔树
堆+BST
性质
- 笛卡尔树的子树是对应序列上一段连续的区间
考虑 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;
}