某次模拟赛 改造二叉树
题目描述 Description |
小Y在学树论时看到了有关二叉树的介绍:在计算机科学中,二叉树是每个结点最多有两个子结点的有序树。通常子结点被称作“左孩子”和“右孩子”。二叉树被用作二叉搜索树和二叉堆。随后他又和他人讨论起了二叉搜索树。 什么是二叉搜索树呢?二叉搜索树首先是一棵二叉树。设key[p]表示结点p上的数值。对于其中的每个结点p,若其存在左孩子lch,则key[p]>key[lch];若其存在右孩子rch,则key[p]<key[rch];注意,本题中的二叉搜索树应满足对于所有结点,其左子树中的key小于当前结点的key,其右子树中的key大于当前结点的key。 小Y与他人讨论的内容则是,现在给定一棵二叉树,可以任意修改结点的数值。修改一个结点的数值算作一次修改,且这个结点不能再被修改。若要将其变成一棵二叉搜索树,且任意时刻结点的数值必须是整数(可以是负整数或0),所要的最少修改次数。 相信这一定难不倒你!请帮助小Y解决这个问题吧。 |
输入描述 Input Description |
第一行一个正整数 n 表示二叉树结点数。结点从 1~n 进行编号。 第二行 n 个正整数用空格分隔开,第 i 个数 ai 表示结点 i 的原始数值。 此后 n - 1 行每行两个非负整数 fa, ch,第 i + 2 行描述结点 i + 1 的父亲编号 fa,以及父子关系 ch,(ch = 0 表示 i + 1 为左儿子,ch = 1 表示 i + 1 为右儿子)。 结点 1 一定是二叉树的根。 |
输出描述 Output Description |
仅一行包含一个整数,表示最少的修改次数。 |
样例输入 Sample Input |
3 |
样例输出 Sample Output |
2 |
数据范围及提示 Data Size & Hint |
20 % :n <= 10 , ai <= 100.
40 % :n <= 100 , ai <= 200 |
之前的一些废话:博客停更一个月,中间出国了两周,然后刷暑假作业,上了一周竞赛,顺便刷完了X战警系列电影。
题解:首先明确一下二叉树的三种遍历方式:先序遍历,中序遍历,后序遍历
void dfs(int o) { //在这里操作是先序遍历 dfs(ch[0][o]); //在这里操作是中序遍历 dfs(ch[1][o]); //在这里操作是后序遍历 }
如果一个二叉树满足二叉搜索树的性质的话,我们发现当且仅当中序遍历的时候是严格上升子序列的时候满足二叉搜索树的性质。所以我们先把中序遍历给计算出来,然后转化成了这样一个问题:对于一个序列,修改其中每个数的数值,问最少几次操作使得原序列变成严格上升子序列。
很容易想到跟最长上升子序列有一些关系,但还是有一点不一样,假如某一段序列是这样的:1,6,7,2,3,4,5,该序列的最长上升子序列为1,2,3,4,5所以需要对6,7进行修改,然而发现一个问题:由于修改后的权值一定是个整数,而夹在1,2之间的数怎么改也改不成。所以我们要对原序列进行修改:考虑序列中两个数Ai,Aj,其中(i<j && Ai<Aj)为了让这两个数中能插进来j-i+1个数,使得原序列变为严格上升的,所以我们得到如下的式子: Aj-Ai+1>=j-i+1 ,再移项得到 Aj-j>=Ai -i。接下来应该就很显然了,把原序列Ai都变成Ai-i,然后我们用O(n log n)的时间内 求出最长不下降子序列即可,最终答案就是n-最长不下降子序列的长度。
代码:
#include<iostream> #include<cmath> #include<cstring> #include<cstdio> #include<queue> #include<algorithm> using namespace std; typedef long long LL; typedef pair<int,int> PII; #define mem(a,b) memset(a,b,sizeof(a)) inline int read() { int x=0,f=1;char c=getchar(); while(!isdigit(c)){if(c=='-')f=-1;c=getchar();} while(isdigit(c)){x=x*10+c-'0';c=getchar();} return x*f; } const int maxn=100010,oo=2147483647; int n,a[maxn],ch[2][maxn],fa[maxn],size[maxn],MAX[maxn],cnt,A[maxn],tot,dp[maxn]; void search(int o) { if(ch[0][o])search(ch[0][o]); A[tot++]=a[o]; if(ch[1][o])search(ch[1][o]); } int main() { n=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=2;i<=n;i++)fa[i]=read(),ch[read()][fa[i]]=i; search(1); for(int i=0;i<n;i++)A[i]=A[i]-i,dp[i]=oo; for(int i=0;i<n;i++)*upper_bound(dp,dp+n,A[i])=A[i]; printf("%d\n",n-(lower_bound(dp,dp+n,oo)-dp)); return 0; }
总结:听xjr说这算是一种差分,基于这种技巧类的东西,一般要积累,也要善于在做题中列以上的式子。