题目描述
给定一棵二叉树,节点标号从1到n(n≤100000)。在不改变其中序遍历的情况下,请改变树的结构,使得这棵二叉树的先序遍历(前序遍历)字典序最小。
输入
第一行一个整数n,表示二叉树的节点数。
接下来n行,每行两个整数。第i行的两个整数表示编号为i的节点的左儿子和右儿子的编号(不存在即为0)。
输出
输出一行n个整数,表示不改变中序遍历的情况下字典序最小的前序遍历序列。
样例输入
5
5 4
0 0
2 1
0 0
0 0
样例输出
1 2 3 5 4
来源 by azui
题解:贪心+分治。
首先处理出树的中序,用数组存起来。
然后每次选出当前区间中的最小值,当作根,再递归按上述方法处理其左右区间。
因为二叉树中序的性质,数组中一个数的左右区间就是它的左子树和右子树。
唯一的问题就是如何快速找出一段区间中的最小值。这里用的是st表,也可以用线段树维护。
#include<cstdio>
const int N=1e5+10;
const int LOG=25;
int n, rot=1, fa[N], son[N][2];
void Root( int &x ) { while( fa[x] ) x=fa[x]; }
int st[N][LOG], lg2[N], note[N], cnt;
#define Cmp( i,j ) ( note[i]<note[j] ? i : j )
void DFS( int r ) {
if( !r ) return;
DFS( son[r][0] );
note[++cnt]=r; st[cnt][0]=cnt;
DFS( son[r][1] );
}
void Pre() {
for( int i=2; i<=n; i++ ) lg2[i]=lg2[i>>1]+1;
for( int j=1; j<=lg2[n]; j++ )
for( int i=1; i+(1<<j)-1<=n; i++ )
st[i][j]=Cmp( st[i][j-1], st[ i+(1<<(j-1)) ][j-1] );
}
void Print( int l, int r ) {
if( l>r ) return;
int k=lg2[r-l+1];
int w=Cmp( st[l][k], st[ r-(1<<k)+1 ][k] );
printf( "%d", note[w] ); cnt++;
cnt<n ? putchar(32) : putchar(10);
Print( l, w-1 ); Print( w+1, r );
}
int main() {
scanf( "%d", &n );
for( int i=1; i<=n; i++ ) {
scanf( "%d%d", &son[i][0], &son[i][1] );
fa[ son[i][0] ]=fa[ son[i][1] ]=i;
}
Root( rot );
DFS( rot );
Pre(); cnt=0;
Print( 1, n );
return 0;
}