题目描述
时间限制:1000ms 内存限制:65536kb (C/C++)
二叉搜索树是一种特殊的二叉树(每个节点最多只有两个儿子的树)。
树的每个节点上存有一个唯一的值,并且满足:这个节点的左子树内所有点的值都比这个节点的值小,且右子树内所有点的值都比这个节点的值要大。
我们定义一棵二叉搜索树
的和值为当前树的所有节点的深度和(根节点的深度为0)。现在有 N 个数需要插入一棵空树中。给定插入序列,请在每个元素被插入之后,输出当前树的和值
。
输入
第一行为一个整数 n
接下来一行是n个各不相同的数字,这些数字在[1, n]区间。
(0<n<3000000<n<300000)
输出
输出 n 行,第 i 行整数表示第 i个数插入树后,当前`树的和值`。
输入样例
8
3 5 1 6 8 7 2 4
输出样例
0
1
2
4
7
11
13
15
题解
为了解决这道题,我们首先需要知道二叉搜索树是什么。关于二叉搜索树的相关介绍已经在题目介绍中有所阐述,下面是一些关于二叉搜索树更多的介绍:
已经了解了二叉树的相关性质,再来思考这道题。首先最简朴的想法当然是模拟二叉搜索树的建立,然后每插入一个数就计算当前树的和值
并输出。典型的暴力求解法。
那么我们看一眼本题的时间限制,1秒,而数据量已经超过百万,所以显然如果使用如上所说的模拟建树的方法来求解本题的答案就必死。
那么如何才能在一秒的时间限制内完成求解呢,我们需要转换一下思想。
对于每一个即将插入二叉搜索树中的数,它的归宿有两种:
- 成为比它大的数中的最小的数的子树
- 成为比它小的数中的最大的数的子树
所以我们的问题转换为,每一个即将插入搜索二叉树中的数,它的深度将是
MAX[depth(比它小的最大的数),depth(比它大的最小的数)]+1
因此对于每一个输入的数,我们并不需要去模拟整个建树的过程,而是直接去寻找这个数的:
前驱
:比它小的最大的数;后继
:比它大的最小的数;
就可以轻松找出depth(x),然后让和值结果sum加上depth(x),输出即可。
而寻找每个即将插入二叉搜索树中的树的前驱
和后继
,我们运用数组双向链表来辅助实现。
创建1~n的双向链表,并将输入序列倒序寻找每个数的前驱
和后继
,完成相关操作后,就将这个数从双向链表中删除。这样就可以实现每一个数的数据采集。
代码如下:
#include <stdio.h>
#define MAXN 300005
int n;
int a[MAXN];
int prev[MAXN], next[MAXN];
int u[MAXN], v[MAXN];
int deep[MAXN];
int main(void)
{
scanf("%d", & n);
for (int i = 1; i <= n; ++i)
scanf("%d", a + i);
for (int i = 1; i <= n; ++i)
{
prev[i] = i - 1;
next[i] = i + 1;
}
int x;
for (int i = n; i > 1; --i)
{
x = a[i];
u[x] = prev[x];
v[x] = next[x];
next[prev[x]] = next[x];
prev[next[x]] = prev[x];
}
long long sum = 0;
for (int i = 1; i <= n; ++i)
{
x = a[i];
if ((u[x] >= 1) && (u[x] <= n))
if (deep[x] < deep[u[x]] + 1)
deep[x] = deep[u[x]] + 1;
if ((v[x] >= 1) && (v[x] <= n))
if (deep[x] < deep[v[x]] + 1)
deep[x] = deep[v[x]] + 1;
sum += deep[a[i]];
printf("%lld\n", sum);
}
return 0;
}