补充:建树过程中会更新lc和rc,这实质上是一个二叉查找树的插入过程。
定义一个玄学节点叫做 R,每次操作读入 val ,执行 Insert(R,val)。
问题:每次 Insert 操作结束之后,输出当前节点的深度和。
这里我们定义 R 节点的深度为 0。
第一行一个整数N(<=3e5),表示操作次数。接下来N行,第i行有一个值val_i,表示第i次操作的val。
输出描述:
N行,每行输出该次操作完后的答案。
样例:
输入:
8
3
5
1
6
8
7
2
4
输出:
0
1
2
4
7
11
13
15
题目解析:
正常地二叉搜索树去做(就是伪代码的做法)会被有序数据卡成n方。
离线做法。考虑记录id(即第几个出现)后的数字排序,可以像中序遍历一样,先找到[l, r]中最早出现的那个,它的ans就是当前深度,然后两边进行分治解决。但如果暴力扫一遍去寻找最早出现的,还是n方的,所以用ST表预处理一下区间最小值。
代码257ms:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 5;
int N;
long long Ans[maxn];
struct V {
int val, id;
bool operator < (const V &rhs) const {
return val < rhs.val;
}
}a[maxn];
int ST[maxn][20], pos[maxn];
void Pre() {
for (int i = 1; i <= N; i++)
ST[i][0] = a[i].id;
for (int j = 1; (1 << j) <= N; j++) {
for (int i = 1; i + (1 << j) - 1 <= N; i++) {
ST[i][j] = min(ST[i][j - 1], ST[i + (1 << (j-1))][j - 1]);
}
}
for (int i = 1; i <= N; i++)
pos[a[i].id] = i;
}
int Query(int l, int r) {
int j = log2(r - l + 1);
return min(ST[l][j], ST[r - (1 << j) + 1][j]);
}
void Divide(int l, int r, int depth) {
if (l > r) return;
int id = Query(l, r);
Ans[id] = depth;
Divide(l, pos[id] - 1, depth + 1);
Divide(pos[id] + 1, r, depth + 1);
}
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) {
scanf("%d", &a[i].val);
a[i].id = i;
}
sort(a + 1, a + 1 + N);
Pre();
Divide(1, N, 0);
for (int i = 1; i <= N; ++i) {
Ans[i] += Ans[i - 1];
printf("%lld\n", Ans[i]);
}
return 0;
}
最后,这套题的其他题目和解析地址:https://www.cnblogs.com/AlphaWA/p/10651045.html