LGP1377 [TJTS 2011] 树的序 学习笔记
LGP1377 [TJTS 2011] 树的序 学习笔记
题意简述
给一个生成序列 \(p\),简单起见 \(p\) 是一个长为 \(n\) 的排列。按照这样的步骤生成一棵二叉搜索树:
- 往空树中插入 \(p_i\),则 \(p_i\) 成为当前二叉搜索树的根。
- 往非空树 \(u\) 中插入 \(p_i\),若 \(p_i\) 小于 \(u\) 则往左子树走,否则往右子树走。
求出字典序最小的生成序列 \(p'\),使得其可以得到与 \(p\) 相同的序列。
做法简述
思考我们怎么变换 \(p\) 可以生成出一样的BST。
发现在最终生成的树中,父亲一定要早于儿子插入,儿子之间顺序可以互换,左儿子早插入更优。
我们把“早晚关系”转化为时间戳的早晚关系。也就是说满足 \(\text{tim}_u<\text{tim}_{ls_u}<\text{tim}_{rs_u}\) 的 \(p'\) 是最优的。
实际上,\(p'\) 正等价于把 \(p\) 生成的BST前序遍历的结果。

现在的目标变为建出 \(p\) 生成的树。朴素做最坏是 \(O(N^2)\) 的,怎么加速呢?
实际上,上述时间戳的早晚关系等价于堆的性质。又因为生成树的值又满足BST性质,所以其可以看作一棵以时间戳为值,\(p_i\) 为键的笛卡尔树。所以我们建完树再跑先序遍历就行了!
代码实现
注意不是 dfs(1) 而是 dfs(P[1])!
#include <bits/stdc++.h>
using namespace std;
namespace obasic{
template <typename _T>
void readi(_T &x){
_T k=1;x=0;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')k=-1;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-'0';
x*=k;return;
}
template <typename _T>
void writi(_T x){
if(x<0)putchar('-'),x=-x;
if(x>9)writi(x/10);
putchar(x%10+'0');
}
};
using namespace obasic;
const int MaxN=1e5+5;
int N,P[MaxN],W[MaxN];
int stk[MaxN],ktp,ls[MaxN],rs[MaxN];
void dfs(int u){
printf("%d ",u);
if(ls[u])dfs(ls[u]);
if(rs[u])dfs(rs[u]);
}
int main(){
readi(N);
for(int i=1;i<=N;i++)readi(P[i]),W[P[i]]=i;
for(int i=1;i<=N;i++){
int k=ktp;
while(k&&W[stk[k]]>W[i])k--;
if(k)rs[stk[k]]=i;
if(k<ktp)ls[i]=stk[k+1];
stk[++k]=i,ktp=k;
}
dfs(P[1]);
return 0;
}
反思总结
“早晚关系”抽象为时间戳大小关系。
拥有一双善于发现堆和BST的眼睛。
浙公网安备 33010602011771号