一月20日
Description
什么是二叉搜索树呢?二叉搜索树首先是一棵二叉树。设 \(key[p]\) 表示结点 \(p\) 上的数值。对于其中的每个结点p,若其存在左孩子 \(lch\) ,则 \(key[p]>key[lch]\) 若其存在右孩子 \(rch\) ,则 \(key[p]<key[rch]\) 注意,本题中的二叉搜索树应满足对于所有结点,其左子树中的 \(key\) 小于当前结点的 \(key\) ,其右子树中的 \(key\) 大于当前结点的 \(key\)。
现在给定一棵二叉树,可以任意修改结点的数值。修改一个结点的数值算作一次修改,且这个结点不能再被修改。若要将其变成一棵二叉搜索树,且任意时刻结点的数值必须是整数(可以是负整数或0),所要的最少修改次数。相信这一定难不倒你!
Input
第一行一个正整数 \(n\) 表示二叉树结点数。结点从 \(1\thicksim n\)进行编号。
第二行 \(n\) 个正整数用空格分隔开,第 \(i\) 个数 \(a_i\) 表示结点 \(i\) 的原始数值。
此后 \(n - 1\) 行每行两个非负整数 \(fa\) , \(ch\) ,第 \(i + 2\) 行描述结点 \(i + 1\) 的父亲编号 \(fa\) ,以及父子关系 \(ch\) ,( \(ch = 0\) 表示 \(i + 1\) 为左儿子,\(ch = 1\) 表示 \(i + 1\) 为右儿子)。
结点1一定是二叉树的根。
Output
仅一行包含一个整数,表示最少的修改次数。
Sample Input
3
2 2 2
1 0
1 1
Sample Output
2
Data Constraint
20 % \(n <= 10 , a_i <= 100\)
40 % \(n <= 100 , a_i <= 200\)
60 % \(n <= 2000\)
100 % \(n <= 10 ^ 5 , a_i < 2 ^ {31}\)
Solution
比赛时我根本没有看懂题目,所以没有比赛时的暴力思路。。。没办法题意太恶心。
正解:
非常妙的一道题。首先如果看到这是一棵树就往树形 \(dp\) 上想那肯定就想不出来了。注意这是一棵二叉搜索树,所以有一些非常奇妙的性质。熟悉平衡树的人应该知道,二叉搜索树的中根遍历是严格上升的。所以我们将中根遍历求出来就好了。现在的问题变成最少修改一个序列上的一些数,使得其严格上升
首先直接求出 \(LIS\) 的长度再用 \(n\) 一减肯定是错的。我们考虑一下反着做,我们设 \(f_i\) 表示前 \(i\) 个数里最多有多少个不用修改。对 \(i\) 位置上的数 \(a_i\) 如果前面有一个位置 \(j\) 满足 \(a_i-a_j>=i-j\) 的话,我们就可以把 \((i,j)\) 上的数全部修改而且能使得其严格递增。所以就有方程
\[f_i=max(f_j+1)[a_i-a_j>=i-j] \]也就是 \(a_i-i>=a_j-j\) 那就是所有的数减掉下标之后求一个最长不下降子序列啊,最后拿 \(n\) 减去这个长度就好了
可以 \(O(nlogn)\) 搞定
\(by\) asuldb
然而 \(O(nlogn)\) 求最长不下降子序列我也不会。
O(nlogn)的算法关键是它建立了一个数组
b[]
,b[i]
表示长度为i
的不下降序列中结尾元素的最小值,用k
表示数组目前的长度,算法完成后k
的值即为最长不下降子序列的长度。具体点来讲:
不妨假设,当前已求出的长度为k
,则判断a[i]
和b[k]
:如果 \(b[k]≤a[i]\) ,即 \(a[i]\) 大于长度为
k
的序列中的最后一个元素,这样就可以使序列的长度增加1,即 \(k=k+1\) ,然后更新 \(b[k]=a[i]\)如果 \(b[k]>a[i]\) ,那么就在 \(b[1] \to b[k]\) 中找到最大的
j
,使得 \(b[j] < a[i]\) ,即a[i]
大于长度为j的序列的最后一个元素,显然,\(b[j+1]≥a[i]\) , 那么就可以更新长度为 \(j+1\) 的序列的最后一个元素,即 \(b[j+1]=a[i]\) 。可以注意到:
b[i]
单调递增,很容易理解,长度更长了,b[k]
的值是不会减小的,更新公式可以用二分查找,所以算法复杂度为 \(O(nlogn)\) 。
\(by\) Shawn-Xue
照着上面的方法打,我调了2个小时没调出来,决定放弃。换一种方法。
准确的说,并不是接在谁后面。而是替换掉谁。因为它接在前面的谁后面都是没有意义的,再接也超不过最长的 \(len\) ,所以是替换掉别人。那么替换掉谁呢?就是替换掉那个最该被它替换的那个。也就是在
d
数组中第一个大于它的。第一个意味着前面的都小于等于它。假设第一个大于它的是d[j]
,说明d[1..j-1]
都小于等于它,那么它完全可以接上d[j-1]
然后生成一个长度为j的不下降子序列,而且这个子序列比当前的d[j]
这个子序列更有潜力(因为这个数比d[j]
小)。所以就替换掉它就行了,也就是 \(d[j]=a[i]\) 。其实这个位置也是它唯一能够替换的位置(前面的替了不满足d[k]
最小值的定义,后面替换了不满足不下降序列)至于第一个大于它的怎么找……STL upper_bound。每次复杂度 \(logn\) 。
\(by\) lvmememe
终于,在我不懈的努力下,调出了这个代码。。。
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll n,key[100005],ch[100005][2];
ll a[100005],dive;
ll b[100005],k;
void dfs(ll x)
{
if(ch[x][0]) dfs(ch[x][0]);
a[++dive]=key[x];
if(ch[x][1]) dfs(ch[x][1]);
}
ll find(ll z)
{
ll l=0,r=k,mid;
while(l+1<r)
{
mid=(l+r)/2;
if(b[mid]<=z) l=mid;
else r=mid;
}
return r;
}
int main()
{
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
scanf("%lld",&key[i]);
for(ll i=2,x,y;i<=n;i++)
{
scanf("%lld%lld",&x,&y);
ch[x][y]=i;
}
dfs(1);
for(ll i=1;i<=n;i++)
a[i]-=i;
b[0]=-2e9;
for(ll i=1,j;i<=n;i++)
{
if(b[k]<=a[i]) b[++k]=a[i];
else
{
j=find(a[i]);
b[j]=a[i];
}
}
printf("%lld\n",n-k);
return 0;
}