题意
给出一棵二叉树,它每个节点上的权值都是整数,我们可以把一个节点上的值任意修改,算作一次修改。求出把这棵二叉树变成BSTBSTBST的最少修改次数。
思路
根据题目的性质,BSTBSTBST的左孩子<<<根<<<右孩子,能容易发现它的中序遍历应该是一个严格递增的序列。
我们求出给出的二叉树的中序遍历,考虑如何修改最少的点使得这个序列严格递增。那么要修改最少的点,就应该让最多的点不被修改,这就变成了求最长上升子序列。
这里举一个例子,如果树的中序遍历为:
1 2 3 1 4 5 61\ 2\ 3\ 1\ 4\ 5\ 61 2 3 1 4 5 6
求出来的最长上升子序列为1 2 3 4 5 61\ 2\ 3\ 4\ 5\ 61 2 3 4 5 6,只有111个点不用修改,可题目中说了每个节点上的值都是正整数,那么我们在dpdpdp的时候,条件应该从:
aj<aia_j<a_iaj<ai
改成:
aj+i−j−1<aia_j+i-j-1<a_iaj+i−j−1<ai
使得从aja_jaj开始填连续上升的一段数后,终点是<ai<a_i<ai的。
这样开始dpdpdp时间复杂度为O(n2)O(n^2)O(n2),只有606060分。
不过改写一下条件可以变成:
aj+i−j≤aia_j+i-j\leq a_iaj+i−j≤ai
aj−j≤ai−ia_j-j\leq a_i-iaj−j≤ai−i
我们就可以一开始让aia_iai减去iii,然后用O(n log n)O(n\ log\ n)O(n log n)的算法求LISLISLIS就好了。
代码
#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);
}