这个算法还是挺人性化的,没有什么难度 就是可能看起来有点晕什么的。

大体 思想是 利用重链刨分来优化子树内部的查询。

考虑一个问题要对每个子树都要询问一次。我们暴力显然是\(n^2\)的。

考虑一下优化这个过程,我们发现儿子的信息可以给父亲用但是不能给兄弟或兄弟里的儿子用。

如果是最大最小值我们只能暴力来搞 但如果是出现次数什么的我们可以利用捅差分来解决这个事情。

考虑我们每次先暴力扫轻儿子然后 再做重儿子然后再把轻儿子的代价加上算当前节点的代价然后再把轻儿子的代价给删掉。

我们发现轻儿子被加上删掉两次 而重儿子只做一次并且保留。

可以发现这样做的复杂度很低 考虑一个点到根有logn条轻边所以这样最坏一个点被暴力来回扫logn次 统计自身答案的时候被扫了1次。

最终复杂度为nlogn 说起来很容易但其实代码还是存在一些细节的 要想好再写。

例题:CF600ELomsat gelral

  1. const int MAXN=100010;
  2. int n,len,mx;
  3. int a[MAXN],cnt[MAXN],root[MAXN],sz[MAXN],son[MAXN];
  4. int lin[MAXN],nex[MAXN<<1],ver[MAXN<<1];ll ans[MAXN],w;
  5. inline void add(int x,int y)
  6. {
  7. ver[++len]=y;
  8. nex[len]=lin[x];
  9. lin[x]=len;
  10. }
  11. inline void dfs(int x,int father)
  12. {
  13. sz[x]=1;
  14. for(int i=lin[x];i;i=nex[i])
  15. {
  16. int tn=ver[i];
  17. if(tn==father)continue;
  18. dfs(tn,x);
  19. sz[x]+=sz[tn];
  20. if(sz[son[x]]<sz[tn])son[x]=tn;
  21. }
  22. }
  23. inline void update(int x,int father,int op,int target)
  24. {
  25. cnt[a[x]]+=op;
  26. if(op>0&&cnt[a[x]]>=mx)
  27. {
  28. if(cnt[a[x]]==mx)w+=a[x];
  29. else w=a[x],mx=cnt[a[x]];
  30. }
  31. for(int i=lin[x];i;i=nex[i])
  32. {
  33. int tn=ver[i];
  34. if(tn==father||tn==target)continue;
  35. update(tn,x,op,target);
  36. }
  37. }
  38. inline void dfs(int x,int father,int op)
  39. {
  40. for(int i=lin[x];i;i=nex[i])
  41. {
  42. int tn=ver[i];
  43. if(tn==father||tn==son[x])continue;
  44. dfs(tn,x,0);//处理轻儿子的答案且清除
  45. }
  46. if(son[x])dfs(son[x],x,1);//处理重儿子的答案
  47. update(x,father,1,son[x]);//把轻儿子的代价加入
  48. ans[x]=w;//答案
  49. if(!op)update(x,father,-1,0),w=0,mx=0;//当前是轻儿子所以删掉
  50. }
  51. int main()
  52. {
  53. freopen("1.in","r",stdin);
  54. n=read();
  55. for(int i=1;i<=n;++i)a[i]=read();
  56. for(int i=1;i<n;++i)
  57. {
  58. int x,y;
  59. x=read();y=read();
  60. add(x,y);add(y,x);
  61. }
  62. dfs(1,0);
  63. dfs(1,0,1);
  64. for(int i=1;i<=n;++i)printf("%lld ",ans[i]);
  65. return 0;
  66. }

虽然这道题可以使用线段树合并来做但是那样对空间和时间的花销都是nlogn的 所以dsu on tree在空间上要优于线段树合并。

且 常数上也必然小于线段树合并。

我们只是关注与dsu on tree的思想 使用重链刨分来进行优化。

再来一道简单的题目来简单再看一下:CF208E Blood Cousins

求有多少个点和某个点的K级祖先相同。不难想到先求出K级祖先然后求出K级祖先子树内深度为x的点的个数。

关于K级祖先的求法:可以倍增+长链刨分优化实现O(1)但是仅对这道题就没什么必要了 询问和n同阶。

可以直接倍增搞,我们还有更快的方法:离线 我们dfs一个点然后加到栈中我们维护某条链上的点。

询问直接查栈中的从前往后第K个元素即可。

考虑第二问 求子树内深度为x的点的个数。显然 dsu on tree。

当然可以使用线段树合并,还有更快的方法:离线 开捅统计对于询问进行捅内外的差分。综上这道题被离线干成了O(n).

为了学习dsu on tree这里使用dsu on tree.

这道题 离线大法好 又得到了一个求树上K级祖先的方法 离线开栈。

  1. const int MAXN=100010;
  2. int n,len,mx,tot,top,m;
  3. int a[MAXN],s[MAXN],cnt[MAXN],root[MAXN],sz[MAXN],son[MAXN];
  4. int lin[MAXN],nex[MAXN],ver[MAXN],d[MAXN],ans[MAXN];
  5. vector<pii>g[MAXN],w[MAXN];
  6. inline void add(int x,int y)
  7. {
  8. ver[++len]=y;
  9. nex[len]=lin[x];
  10. lin[x]=len;
  11. }
  12. inline void dfs(int x,int father)
  13. {
  14. d[x]=d[father]+1;sz[x]=1;
  15. s[++top]=x;
  16. for(int i=0;i<g[x].size();++i)
  17. {
  18. int tn=g[x][i].F;
  19. if(tn>d[father])continue;
  20. int ss=s[top-tn];
  21. w[ss].push_back(mk(d[x],g[x][i].S));
  22. }
  23. for(int i=lin[x];i;i=nex[i])
  24. {
  25. int tn=ver[i];
  26. if(tn==father)continue;
  27. dfs(tn,x);
  28. sz[x]+=sz[tn];
  29. if(sz[son[x]]<sz[tn])son[x]=tn;
  30. }
  31. --top;
  32. }
  33. inline void update(int x,int father,int op,int target)
  34. {
  35. cnt[d[x]]+=op;
  36. for(int i=lin[x];i;i=nex[i])
  37. {
  38. int tn=ver[i];
  39. if(tn==father||tn==target)continue;
  40. update(tn,x,op,target);
  41. }
  42. }
  43. inline void dfs(int x,int father,int op)
  44. {
  45. for(int i=lin[x];i;i=nex[i])
  46. {
  47. int tn=ver[i];
  48. if(tn==father||tn==son[x])continue;
  49. dfs(tn,x,0);
  50. }
  51. if(son[x])dfs(son[x],x,1);
  52. update(x,father,1,son[x]);
  53. for(int i=0;i<w[x].size();++i)
  54. ans[w[x][i].S]+=cnt[w[x][i].F];
  55. if(!op)update(x,father,-1,0);
  56. }
  57. int main()
  58. {
  59. freopen("1.in","r",stdin);
  60. n=read();
  61. for(int i=1;i<=n;++i)
  62. {
  63. int x=read();
  64. if(!x)root[++tot]=i;
  65. else add(x,i);
  66. }
  67. m=read();
  68. for(int i=1;i<=m;++i)
  69. {
  70. int x,y;
  71. x=read();y=read();
  72. g[x].push_back(mk(y,i));
  73. }
  74. for(int i=1;i<=tot;++i)dfs(root[i],0);
  75. for(int i=1;i<=tot;++i)dfs(root[i],0,0);//dsu on tree
  76. rep(1,m,i)printf("%d ",ans[i]?ans[i]-1:0);
  77. return 0;
  78. }

dsu on tree详解的更多相关文章

  1. [CF1009F] Dominant Indices (+dsu on tree详解)

    这道题用到了dsu(Disjoint Set Union) on tree,树上启发式合并. 先看了CF的官方英文题解,又看了看zwz大佬的题解,差不多理解了dsu on tree的算法. 但是时间复 ...

  2. 【算法】关于图论中的最小生成树(Minimum Spanning Tree)详解

    本节纲要 什么是图(network) 什么是最小生成树 (minimum spanning tree) 最小生成树的算法 什么是图(network)? 这里的图当然不是我们日常说的图片或者地图.通常情 ...

  3. 二叉查找树(binary search tree)详解

    二叉查找树(Binary Search Tree),也称二叉排序树(binary sorted tree),是指一棵空树或者具有下列性质的二叉树: 若任意节点的左子树不空,则左子树上所有结点的值均小于 ...

  4. BTree和B+Tree详解

    https://www.cnblogs.com/vianzhang/p/7922426.html B+树索引是B+树在数据库中的一种实现,是最常见也是数据库中使用最为频繁的一种索引.B+树中的B代表平 ...

  5. ODT(old driver tree)详解(带例题)

    文章目录 ODT简介 实现前提&&实现原理 初始化 split操作 assign操作 其它操作 区间第k小 区间加 区间所有数的k次方和 几道水题 ODT简介 ODT(old driv ...

  6. 数据结构31:树(Tree)详解

    复制广义表数据结构中的树 树是数据结构中比较重要也是比较难理解的一类存储结构.本章主要主要围绕二叉树,对树的存储以及遍历做详细的介绍,同时还会涉及到有关树的实际应用,例如构建哈弗曼编码等. 由于树存储 ...

  7. 【2018.9.26】K-D Tree详解

    网上对K-D-Tree的讲解不尽清晰,我学了很久都不会写,这里新开一文做一些讲解. 1.K-D-Tree是什么? K-DTree 即 K-Dimensional-Tree,常用来作空间划分及近邻搜索, ...

  8. dsu on tree (树上启发式合并) 详解

    一直都没出过算法详解,昨天心血来潮想写一篇,于是 dsu on tree 它来了 1.前置技能 1.链式前向星(vector 建图) 2.dfs 建树 3.剖分轻重链,轻重儿子 重儿子 一个结点的所有 ...

  9. Ext.Net学习笔记22:Ext.Net Tree 用法详解

    Ext.Net学习笔记22:Ext.Net Tree 用法详解 上面的图片是一个简单的树,使用Ext.Net来创建这样的树结构非常简单,代码如下: <ext:TreePanel runat=&q ...

随机推荐

  1. js 图片压缩上传(base64位)以及上传类型分类

    一.input file上传类型 1.指明只需要图片 <input type="file" accept='image/*'> 2.指明需要多张图片 <input ...

  2. The Meaningless Game,算是思维吧。

    题目直接链接 题意: 某游戏规则:每次选定数字k(正整数),两人初始分数为1,获胜者分数乘k2,失败者分数成k,给你两个数字,判断是否可能是本游戏的两人的得分. 分析: 为啥题意我不写判断两个数可不可 ...

  3. 一个简单的webAPI调用

    1.新建一个ASP.NET Web应用程序. 2.选择空模板,WebAPI. 3.在Models文件夹添加Product类. 4.添加空控制器ProductController. 5.ProductC ...

  4. Pop!_OS下安装C++编程工具

    Pop!_OS下C++编程 #0x0 Visual Studio Code #0x1 C++ 0x11 code::blocks #0x0 Visual Studio Code 下载安装vscode ...

  5. 使用Rancher在K8S上部署高性能PHP应用程序

    介 绍 PHP是网络上最流行的编程语言之一,许多被广泛使用的内容管理系统都使用它开发,如WordPress和Drupal,并为现代服务器端框架(如Laravel和Symfony)提供核心代码. 尽管P ...

  6. CRLF injection 简单总结

    CRLF injection 简单总结 简介 CRLF是"回车 + 换行"(\r\n)的简称,即我们都知道在HTTP协议中,HTTP Header与HTTP Body是用两个CRL ...

  7. 数据可视化实例(七): 计数图(matplotlib,pandas)

    https://datawhalechina.github.io/pms50/#/chapter5/chapter5 计数图 (Counts Plot) 避免点重叠问题的另一个选择是增加点的大小,这取 ...

  8. 渐进式Web应用(PWA)

    什么是渐进式Web应用? 渐进式Web应用是一种全新的Web技术,让Web应用和原生APP的体验相近或一致. 渐进式Web应用它可以横跨Web技术及Native APP开发的解决方案,对于开发者的优势 ...

  9. Docker搭建部署Node项目

    前段时间做了个node全栈项目,服务端技术栈是 nginx + koa + postgresql.其中在centos上搭建环境和部署都挺费周折,部署测试服务器,接着上线的时候又部署生产环境服务器.这中 ...

  10. elementui 使用Form表单 的 resetForm表单功能出现的问题

    代码因为在保密机上,这里只进行描述并截取elemen文档中的代码作为参考 今天在开发一个很简单需求的时候遇到的问题,在使用elementui的表单功能,将增和改的表单进行了复用,是在表单的父组件 dr ...