题意
给出一棵二叉树,它每个节点上的权值都是整数,我们可以把一个节点上的值任意修改,算作一次修改。求出把这棵二叉树变成 B S T BST BST的最少修改次数。
思路
根据题目的性质, B S T BST BST的左孩子 < < <根 < < <右孩子,能容易发现它的中序遍历应该是一个严格递增的序列。
我们求出给出的二叉树的中序遍历,考虑如何修改最少的点使得这个序列严格递增。那么要修改最少的点,就应该让最多的点不被修改,这就变成了求最长上升子序列。
这里举一个例子,如果树的中序遍历为:
1
2
3
1
4
5
6
1\ 2\ 3\ 1\ 4\ 5\ 6
1 2 3 1 4 5 6
求出来的最长上升子序列为
1
2
3
4
5
6
1\ 2\ 3\ 4\ 5\ 6
1 2 3 4 5 6,只有
1
1
1个点不用修改,可题目中说了每个节点上的值都是正整数,那么我们在
d
p
dp
dp的时候,条件应该从:
a
j
<
a
i
a_j<a_i
aj<ai
改成:
a
j
+
i
−
j
−
1
<
a
i
a_j+i-j-1<a_i
aj+i−j−1<ai
使得从
a
j
a_j
aj开始填连续上升的一段数后,终点是
<
a
i
<a_i
<ai的。
这样开始
d
p
dp
dp时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),只有
60
60
60分。
不过改写一下条件可以变成:
a
j
+
i
−
j
≤
a
i
a_j+i-j\leq a_i
aj+i−j≤ai
a
j
−
j
≤
a
i
−
i
a_j-j\leq a_i-i
aj−j≤ai−i
我们就可以一开始让 a i a_i ai减去 i i i,然后用 O ( n l o g n ) O(n\ log\ n) O(n log n)的算法求 L I S LIS LIS就好了。
代码
#include<cstdio>
#include<algorithm>
int n, tot;
int a[100001], lson[100001], rson[100001], dfn[100001], f[100001];
void dfs(int p) {
if (lson[p]) dfs(lson[p]);
dfn[++tot] = a[p];
if (rson[p]) dfs(rson[p]);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for (int i = 2; i <= n; i++) {
int fa, ch;
scanf("%d %d", &fa, &ch);
if (!ch) lson[fa] = i;
else rson[fa] = i;
}
dfs(1);
for (int i = 1; i <= n; i++) dfn[i] -= i;
int len = 1;
f[1] = dfn[1];
for (int i = 2; i <= n; i++) {
if (dfn[i] >= f[len]) f[++len] = dfn[i];
else f[std::upper_bound(f + 1, f + len + 1, dfn[i]) - f] = dfn[i];
}
printf("%d", n - len);
}