我们考虑将元素按照键值 k 排序。然后一个一个插入到当前的笛卡尔树中。那么每次我们插入的元素必然在这个树的右链(右链:即从根结点一直往右子树走,经过的结点形成的链)的末端。于是我们执行这样一个过程,从下往上比较右链结点与当前结点 u 的 w,如果找到了一个右链上的结点 x 满足 xw<uw,就把 u 接到 x 的右儿子上,而 x 原本的右子树就变成 u 的左子树。
新建一个大小为 n 的空栈。用 top 来标操作前的栈顶,k 来标记当前栈顶。
For i := 1 to n
k := top
While 栈非空 且 栈顶元素 > 当前元素
k--
if 栈非空
栈顶元素.右儿子 := 当前元素
if k < top
当前元素.左儿子 := 上一个被弹出的元素
当前元素入栈
top := k
for(int i =1; i <= n; i++){int k = top;while(k >0&& h[stk[k]]> h[i]) k--;if(k) rs[stk[k]]= i;// rs代表笛卡尔树每个节点的右儿子if(k < top) ls[i]= stk[k +1];// ls代表笛卡尔树每个节点的左儿子
stk[++k]= i;
top = k;}
这道题你可 DP,可单调栈,但你万万没想到的是它也可以笛卡尔树!具体地,我们把下标作为键值 k,hi 作为键值 w 满足小根堆性质,构建一棵 (i,hi) 的笛卡尔树。
这样我们枚举每个结点 u,把 uw(即结点 u 的高度键值 h)作为最大子矩阵的高度。由于我们建立的笛卡尔树满足小根堆性质,因此 u 的子树内的结点的高度都大于等于 u。而我们又知道 u 子树内的下标是一段连续的区间。于是我们只需要知道子树的大小,然后就可以算这个区间的最大子矩阵的面积了。用每一个点计算出来的值更新答案即可。显然这个可以一次 DFS 完成,因此复杂度仍是 O(n) 的。
#include<algorithm>#include<cstdio>#include<cstring>#include<iostream>usingnamespace std;typedeflonglong ll;constint N =100000+10, INF =0x3f3f3f3f;structnode{int idx, val, par, ch[2];friendbooloperator<(node a, node b){return a.idx < b.idx;}voidinit(int _idx,int _val,int _par){
idx = _idx, val = _val, par = _par, ch[0]= ch[1]=0;}} tree[N];int root, top, stk[N];
ll ans;intcartesian_build(int n){// 建树,满足小根堆性质for(int i =1; i <= n; i++){int k = i -1;while(tree[k].val > tree[i].val) k = tree[k].par;
tree[i].ch[0]= tree[k].ch[1];
tree[k].ch[1]= i;
tree[i].par = k;
tree[tree[i].ch[0]].par = i;}return tree[0].ch[1];}intdfs(int x){// 一次dfs更新答案就可以了if(!x)return0;int sz =dfs(tree[x].ch[0]);
sz +=dfs(tree[x].ch[1]);
ans =max(ans,(ll)(sz +1)* tree[x].val);return sz +1;}intmain(){int n, hi;while(scanf("%d",&n), n){
tree[0].init(0,0,0);for(int i =1; i <= n; i++){scanf("%d",&hi);
tree[i].init(i, hi,0);}
root =cartesian_build(n);
ans =0;dfs(root);printf("%lld\n", ans);}return0;}