[关闭]
@AkamemakA 2022-07-22T12:44:32.000000Z 字数 2700 阅读 120

题解 杂题 数据结构

成外集训考试题 洗


题目背景

二叉排序树:

一棵二叉树为一颗二叉排序树,当且仅当所有的节点(叶子结点除外)的左子树上的所有点的值都小于此节点的值,且所有右节点的值都大于此节点的值。

题目大意

有一棵二叉树,每个节点有一个权值。问最少要修改多少个点的权值(只能修改为整数),才能使这棵树变为一颗二叉排序树。输出最小修改次数。

第一行一个整数,第二行个整数表示个点的权值。接下来行输入两个数,第行第一个数表示节点的父亲为,第二个数表示左右关系。若,则的左儿子。若 ,则为右儿子。

样例输入

  1. 3
  2. 2 2 2
  3. 1 0
  4. 1 1

样例输出

  1. 2

样例解释

将节点的值修改为,将节点的值修改为

数据范围


解析

对于二叉排序树的定义,我们不难想到如果对于一颗二叉排序树进行中序遍历,那么得到的序列一定是一个严格递增的序列。

因此,我们要做的就是对于输入的一棵树,将其进行中序遍历,再用最小的操作次数将其修改为严格递增的即可。

所以不难想到,只需要找到遍历出的数组的最长上升子序列的长度,再用总长减去即可。

但是这样做会有一个问题,例如遍历出来的权值为:,那么按上述做法,找到了最长上升子序列,即要修改的值。但题中有规定,权值只能修改为整数,因此只修改是无法达到要求的。

因此,我们来做进一步考虑。

不难发现,如果要将之间的所有数改为递增,那么必须满足一个条件:

表示之间的数的个数应不大于两权值之差,这样才能满足条件。如上述要修改之间的数,但,因此不能修改。

我们再将此式化简,得到:

即要修改之间的值必须满足以上条件。

我们定义,那么这个问题就很好办了:我们只需要求出数组,再找到的最长不下降子序列()的长度,再用总长减去即可。

  1. #include<iostream>
  2. using namespace std;
  3. const int N=2e5+5;
  4. int n;
  5. int a[N],f[N],b[N],tot;
  6. int tree[N][2];
  7. inline void dfs(int u){
  8. if(tree[u][0]) dfs(tree[u][0]);
  9. b[++tot]=a[u]-tot; //同时求出b数组
  10. if(tree[u][1]) dfs(tree[u][1]);
  11. }
  12. inline int lis(){
  13. int res=0;
  14. f[1]=1;
  15. for(int i=2;i<=n;i++){
  16. f[i]=1;
  17. for(int j=1;j<i;j++)
  18. if(b[i]>=b[j]) f[i]=max(f[i],f[j]+1);
  19. res=max(res,f[i]);
  20. }
  21. return res; //LIS模板,没什么好说的
  22. }
  23. int main()
  24. {
  25. cin>>n;
  26. for(int i=1;i<=n;i++)
  27. cin>>a[i];
  28. for(int i=2;i<=n;i++){
  29. int x,f;
  30. cin>>x>>f;
  31. tree[x][f]=i;
  32. }
  33. dfs(1); //中序遍历
  34. cout<<n-lis();
  35. }

不过普通的时间复杂度趋近于,但此题 ,因此,这份代码只能过%的数据。

接下来,我们考虑优化。

谈到优化,那就是令人脑袋疼的数据结构优化了。线段树、树状数组、都可以来解决此问题(玄学卡常就免了)。这里,我们来介绍树状数组的做法。

观察上述代码第行,我们发现,对于以结尾的是由之前的最大的转移而来。因此我们可以用树状数组维护最大值。具体理论可以参考其他大佬讲解。

对于每一个数,先查出小于这个数结尾的的的最大值,那么
然后再把以结尾的的最大值插入到树状数组中。利用树状数组维护这一过程。

由于非常大,普通的数组下标存不下。因此,要将离散化。


代码实现

  1. #include<iostream>
  2. #include<vector>
  3. #include<algorithm>
  4. using namespace std;
  5. #define inv(x) (lower_bound(v.begin(),v.end(),x)-v.begin()+1) //求离散化后的映射值
  6. const int N=1e5+5;
  7. int n,a[N];
  8. int tree[N][2];
  9. int b[N],tot,c[N];
  10. int f[N];
  11. vector<int> v; //离散数组
  12. inline int lowbit(int x){
  13. return x& (-x);
  14. }
  15. inline void dfs(int u){
  16. if(tree[u][0]) dfs(tree[u][0]);
  17. b[++tot]=a[u]-tot;
  18. if(tree[u][1]) dfs(tree[u][1]);
  19. }
  20. inline void lisan(){//离散化
  21. for(int i=1;i<=n;i++)
  22. v.push_back(b[i]);
  23. sort(v.begin(),v.end());
  24. v.erase(unique(v.begin(),v.end()),v.end());
  25. }
  26. inline int query(int x){ //找到小于b[x]结尾的LIS的最大值
  27. int res=0;
  28. for(int i=x;i;i-=lowbit(i))
  29. res=max(res,c[i]);
  30. return res;
  31. }
  32. inline void updata(int x,int y){//更新最大值
  33. for(int i=x;i<=n;i+=lowbit(i))
  34. c[i]=max(c[i],y);
  35. }
  36. inline int lis(){
  37. int res=0;
  38. for(int i=1;i<=n;i++){
  39. b[i]=inv(b[i]);
  40. f[i]=query(b[i])+1;
  41. updata(b[i],f[i]);
  42. res=max(res,f[i]);
  43. }
  44. return res;
  45. }
  46. int main()
  47. {
  48. cin>>n;
  49. for(int i=1;i<=n;i++)
  50. cin>>a[i];
  51. for(int i=2;i<=n;i++){
  52. int u,x;
  53. cin>>u>>x;
  54. tree[u][x]=i;
  55. }
  56. dfs(1);
  57. lisan();
  58. cout<<n-lis();
  59. }

完结撒花~

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注