dsu on tree,又名树上启发式合并、重链剖分,是一类十分实用的trick,它常常可以作为一些正解的替代算法:

1.DFS序+线段树/主席树/线段树合并

2.对DFS序分块的树上莫队

3.长链剖分(但复杂度会多一个log)

4.点分治(通常可以做有根树的点分治)

重链剖分的概念,用一个DFS找到每个点最大的一个儿子,作为它的重儿子,并将它标记。则从上到下一段连续的标记点就成为一条重链。

重链剖分有一个常用的性质:每个点到根的路径上,至多经过$O(\log n)$条重链。点分治、树链剖分都用到了这个性质。

dsu on tree 是一个优化后的暴力,主要优化的地方在于它先递归轻子树并消除影响,后递归重子树并保留影响。之后再计算该节点需要的信息。

它可以解决大部分无修改子树查询问题,需要问题满足以下几个条件:

1.有一个树上的$O(n^2)$暴力算法。

2.从轻子树合并上来的复杂度是线性的。

3.从重子树合并上来的复杂度是$O(1)$的。

4.可以在O(子树大小)时间内清空递归后的数组(也就是线性撤销所有影响)

dsu on tree的流程:

1.递归到所有轻儿子并消除影响。

2.递归到重儿子并保留影响。

3.递归所有轻儿子计算子树内除重子树之外的点对当前点答案的影响。

4.若此点不是父亲的重儿子则消除子树内所有影响(即将数组清空)。

另外,dsu on tree的题,只要使用的暴力数组是以深度为下标的,几乎都可以被长链剖分替代且复杂度少一个log。如下面的例2,3。

例一:[CF600E]询问每棵子树中出现次数最多的颜色(可能不只一个)的编号和。

首先考虑暴力算法,对每个点x DFS下去,得到一个计数器数组co[i]表示x的子树内颜色i的点的个数,同时维护x的答案。

然后重链剖分,按流程做即可,具体模板见代码。

  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<iostream>
  4. #define rep(i,l,r) for (int i=(l); i<=(r); i++)
  5. #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
  6. typedef long long ll;
  7. using namespace std;
  8.  
  9. const int N=;
  10. int n,u,v,cnt,tot[N],mx,col[N],sz[N],son[N],h[N],to[N],nxt[N];
  11. ll ans[N],sm;
  12. bool skip[N];
  13.  
  14. void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
  15.  
  16. void get(int x,int fa){
  17. sz[x]=;
  18. For(i,x) if ((k=to[i])!=fa){
  19. get(k,x); sz[x]+=sz[k];
  20. if (sz[k]>sz[son[x]]) son[x]=k;
  21. }
  22. }
  23.  
  24. void dfs(int x,int fa,int op){
  25. tot[col[x]]+=op;
  26. if (op> && tot[col[x]]>=mx){
  27. if (tot[col[x]]>mx) sm=,mx=tot[col[x]];
  28. sm+=col[x];
  29. }
  30. For(i,x) if ((k=to[i])!=fa && !skip[k]) dfs(k,x,op);
  31. }
  32.  
  33. void work(int x,int fa,bool cl){
  34. For(i,x) if ((k=to[i])!=fa && k!=son[x]) work(k,x,);
  35. if (son[x]) work(son[x],x,),skip[son[x]]=;
  36. dfs(x,fa,); ans[x]=sm; skip[son[x]]=;
  37. if (cl) dfs(x,fa,-),mx=sm=;
  38. }
  39.  
  40. int main(){
  41. scanf("%d",&n);
  42. rep(i,,n) scanf("%d",&col[i]);
  43. rep(i,,n) scanf("%d%d",&u,&v),add(u,v),add(v,u);
  44. get(,); work(,,);
  45. rep(i,,n) cout<<ans[i]<<' ';
  46. return ;
  47. }

CF600E

例二:[CF570D]一棵树,询问某棵子树指定深度的点能否构成回文。

用二进制记录每个深度的每个字母出现次数的奇偶性即可。

  1. #include<cstdio>
  2. #include<vector>
  3. #include<algorithm>
  4. #define rep(i,l,r) for (int i=(l); i<=(r); i++)
  5. #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
  6. using namespace std;
  7.  
  8. const int N=;
  9. char s[N];
  10. int n,m,x,y,cnt,skip,a[N],d[N],ans[N],fa[N],sz[N],son[N],to[N],nxt[N],h[N];
  11. struct P{ int x,y; };
  12. vector<P>ve[N];
  13.  
  14. void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
  15.  
  16. void dfs(int x){
  17. d[x]=d[fa[x]]+; sz[x]=;
  18. For(i,x){
  19. dfs(k=to[i]); sz[x]+=sz[k];
  20. if (sz[k]>sz[son[x]]) son[x]=k;
  21. }
  22. }
  23.  
  24. void get(int x){
  25. a[d[x]]^=<<(s[x]-'a');
  26. For(i,x) if ((k=to[i])!=skip) get(k);
  27. }
  28.  
  29. void work(int x,int op){
  30. For(i,x) if ((k=to[i])!=son[x]) work(k,);
  31. if (son[x]) work(son[x],),skip=son[x];
  32. get(x); skip=; int ed=ve[x].size()-;
  33. rep(i,,ed) ans[ve[x][i].x]=__builtin_popcount(a[ve[x][i].y])<=;
  34. if (op) get(x);
  35. }
  36.  
  37. int main(){
  38. freopen("570D.in","r",stdin);
  39. freopen("570D.out","w",stdout);
  40. scanf("%d%d",&n,&m);
  41. rep(i,,n) scanf("%d",&fa[i]),add(fa[i],i);
  42. rep(i,,n) scanf(" %c",&s[i]);
  43. rep(i,,m) scanf("%d%d",&x,&y),ve[x].push_back((P){i,y});
  44. dfs(); work(,);
  45. rep(i,,m) if (ans[i]) puts("Yes"); else puts("No");
  46. return ;
  47. }

CF570D

例三:

[CF246E]一个森林,求k级后代中多少种不同的权值。

[CF208E]给出一个森林,求和一个点有相同k级祖先的点有多少。

第二个问题倍增找到k级祖先后就是弱化版的第一个问题了。

与例二类似,对每个深度维护一个set记录出现过的权值数目。

注意由于流程的最后一步是清空整个数组,所以删除的时候不需要考虑记录次数的问题。

  1. #include<set>
  2. #include<map>
  3. #include<string>
  4. #include<cstdio>
  5. #include<vector>
  6. #include<algorithm>
  7. #include<iostream>
  8. #define rep(i,l,r) for (int i=(l); i<=(r); i++)
  9. #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
  10. using namespace std;
  11.  
  12. const int N=;
  13. string s;
  14. map<string,int>mp;
  15. int n,Q,x,y,tot,skip,cnt,rts,id[N],fa[N],rt[N],ans[N],sz[N];
  16. int son[N],d[N],h[N],to[N<<],nxt[N<<];
  17. set<int>S[N];
  18. struct P{ int x,y; };
  19. vector<P>ve[N];
  20. void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
  21.  
  22. void dfs(int x){
  23. d[x]=d[fa[x]]+; sz[x]=;
  24. For(i,x){
  25. dfs(k=to[i]); sz[x]+=sz[k];
  26. if (sz[k]>sz[son[x]]) son[x]=k;
  27. }
  28. }
  29.  
  30. void get(int x,int op){
  31. if (op) S[d[x]].insert(id[x]); else S[d[x]].erase(id[x]);
  32. For(i,x) if ((k=to[i])!=skip) get(k,op);
  33. }
  34.  
  35. void work(int x,int op){
  36. For(i,x) if ((k=to[i])!=son[x]) work(k,);
  37. if (son[x]) work(son[x],),skip=son[x];
  38. get(x,); skip=; int ed=ve[x].size()-;
  39. rep(i,,ed) ans[ve[x][i].x]=S[d[x]+ve[x][i].y].size();
  40. if (op) get(x,);
  41. }
  42.  
  43. int main(){
  44. scanf("%d",&n);
  45. rep(i,,n){
  46. cin>>s>>fa[i];
  47. if (mp.find(s)!=mp.end()) id[i]=mp[s]; else mp[s]=id[i]=++tot;
  48. if (!fa[i]) rt[++rts]=i; else add(fa[i],i);
  49. }
  50. scanf("%d",&Q);
  51. rep(i,,Q) scanf("%d%d",&x,&y),ve[x].push_back((P){i,y});
  52. rep(i,,rts) dfs(rt[i]),work(rt[i],);
  53. rep(i,,Q) printf("%d\n",ans[i]);
  54. return ;
  55. }

CF246E

例四:[CF741D]一棵有根树,边上有字母a~v,求每个子树中最长的边,满足这个边上的所有字母重排后可以构成回文。

很容易想到点分治,但由于是有根树,所以点分治是做不了的。

先对没给点记录w[x]表示x到根的路径的各个字母出现奇偶性,然后p[S]记录满足w[x]=S的所有点x的最大深度。

类似点分治,一棵子树一棵子树地处理,每次先对子树内每个点查找之前子树中是否有能与它拼成回文串的点与它的最大深度,再更新p[]。

注意查找和更新不能同时进行,要整棵子树都查询完毕之后再进行更新操作。注意查找与更新当前子树的根。

  1. #include<cstdio>
  2. #include<algorithm>
  3. #define rep(i,l,r) for (int i=(l); i<=(r); i++)
  4. #define For(i,x) for (int i=h[x],k; i; i=nxt[i])
  5. using namespace std;
  6.  
  7. const int N=,inf=1e9;
  8. char s[N];
  9. int n,cnt,d[N],fa[N],ans[N],w[N],sz[N],son[N],p[<<],h[N],to[N<<],nxt[N<<];
  10. void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; }
  11.  
  12. void dfs(int x){
  13. d[x]=d[fa[x]]+; sz[x]=;
  14. For(i,x){
  15. k=to[i]; w[k]=w[x]^(<<(s[k]-'a')); dfs(k); sz[x]+=sz[k];
  16. if (sz[k]>sz[son[x]]) son[x]=k;
  17. }
  18. }
  19.  
  20. void get(int rt,int x){
  21. ans[rt]=max(ans[rt],p[w[x]]+d[x]-*d[rt]);
  22. rep(i,,) ans[rt]=max(ans[rt],p[w[x]^(<<i)]+d[x]-*d[rt]);
  23. For(i,x) get(rt,k=to[i]);
  24. }
  25.  
  26. void get2(int x){ p[w[x]]=max(p[w[x]],d[x]); For(i,x) get2(k=to[i]); }
  27.  
  28. void del(int x){ p[w[x]]=-inf; For(i,x) del(k=to[i]); }
  29.  
  30. void work(int x,int op){
  31. For(i,x) if ((k=to[i])!=son[x]) work(k,),ans[x]=max(ans[x],ans[k]);
  32. if (son[x]) work(son[x],),ans[x]=max(ans[x],ans[son[x]]);
  33. For(i,x) if ((k=to[i])!=son[x]) get(x,k),get2(k);
  34. ans[x]=max(ans[x],p[w[x]]-d[x]);
  35. rep(i,,) ans[x]=max(ans[x],p[w[x]^(<<i)]-d[x]);
  36. p[w[x]]=max(p[w[x]],d[x]);
  37. if (op) del(x);
  38. }
  39.  
  40. int main(){
  41. freopen("741D.in","r",stdin);
  42. freopen("741D.out","w",stdout);
  43. scanf("%d",&n);
  44. rep(i,,n) scanf("%d %c",&fa[i],&s[i]),add(fa[i],i);
  45. rep(i,,(<<)-) p[i]=-inf;
  46. dfs(); work(,);
  47. rep(i,,n) printf("%d ",ans[i]);
  48. return ;
  49. }

CF741D

dsu on tree题表的更多相关文章

  1. [CSP-S模拟测试]:射手座之日(dsu on tree)

    题目传送门(内部题103) 输入格式 第一行一个数$n$,表示结点的个数. 第二行$n–1$个数,第$i$个数是$p[i+1]$.$p[i]$表示结点$i$的父亲是$p[i]$.数据保证$p[i]&l ...

  2. CF 741D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths [dsu on tree 类似点分治]

    D. Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths CF741D 题意: 一棵有根树,边上有字母a~v,求每个子树中最长的边,满 ...

  3. [dsu on tree]【学习笔记】

    十几天前看到zyf2000发过关于这个的题目的Blog, 今天终于去学习了一下 Codeforces原文链接 dsu on tree 简介 我也不清楚dsu是什么的英文缩写... 就像是树上的启发式合 ...

  4. dsu on tree入门

    先瞎扯几句 说起来我跟这个算法好像还有很深的渊源呢qwq.当时在学业水平考试的考场上,题目都做完了不会做,于是开始xjb出题.突然我想到这么一个题 看起来好像很可做的样子,然而直到考试完我都只想出来一 ...

  5. 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree

    原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...

  6. BZOJ.4182.Shopping(点分治/dsu on tree 树形依赖背包 多重背包 单调队列)

    BZOJ 题目的限制即:给定一棵树,只能任选一个连通块然后做背包,且每个点上的物品至少取一个.求花费为\(m\)时最大价值. 令\(f[i][j]\)表示在点\(i\),已用体积为\(j\)的最大价值 ...

  7. Codeforces 741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(dsu on tree)

    感觉dsu on tree一定程度上还是与点分类似的.考虑求出跨过每个点的最长满足要求的路径,再对子树内取max即可. 重排后可以变成回文串相当于出现奇数次的字母不超过1个.考虑dsu on tree ...

  8. [学习笔记]Dsu On Tree

    [dsu on tree][学习笔记] - Candy? - 博客园 题单: 也称:树上启发式合并 可以解决绝大部分不带修改的离线询问的子树查询问题 流程: 1.重链剖分找重儿子 2.sol:全局用桶 ...

  9. Codeforces.600E.Lomsat gelral(dsu on tree)

    题目链接 dsu on tree详见这. \(Description\) 给定一棵树.求以每个点为根的子树中,出现次数最多的颜色的和. \(Solution\) dsu on tree模板题. 用\( ...

随机推荐

  1. PHP魔术方法之__invoke()

    将对象当作函数来使用时,会自动调用该方法. class ShowProfile extends Controller { public function __invoke($id) { return ...

  2. ASP.NET MVC学习(一)之路由篇Route

    什么是路由 通过[路由]配置,路由可以规定URL的特殊格式,使其达到特殊效果. 在ASP.NET MVC框架中,通过路由配置URL,使用户的URL请求可以映射到Controller下的action方法 ...

  3. 20155304 2016-2017-2 《Java程序设计》第七周学习总结

    20155304 2016-2017-2 <Java程序设计>第七周学习总结 教材学习内容总结 1.时间的度量: 格林威治标准时间(GMT)通过观察太阳而得,其正午是太阳抵达天空最高点之时 ...

  4. python爬虫-图片批量下载

    # 爬起摄图网的图片批量下载# coding:utf-8 import requests from bs4 import BeautifulSoup from scipy.misc import im ...

  5. Spring4总结

    1. 明确Spring的主要作用就是为代码“解耦”,降低代码间的耦合度.使用IoC使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了,而是由Spring容器统一管理,自动 ...

  6. order by 字段自动填写脚本

    新版 firefox 中的 hackbar 没有 order by 字段填写, 所以就有了这个: =begin pod sql注入中自动输出order by 的位数 =end pod sub MAIN ...

  7. mount过程分析之一(基于3.16.3内核)【转】

    转自:https://blog.csdn.net/zr_lang/article/details/39963253 一直想写有些关于文件系统的博文,但是由于近一年来实在太忙,所以没有时间写.前几日赶上 ...

  8. 002_分布式搜索引擎Elasticsearch的查询与过滤

    一.写入 先来一个简单的官方例子,插入的参数为-XPUT,插入一条记录. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 curl -XPUT 'http:/ ...

  9. 09 Go 1.9 Release Notes

    Go 1.9 Release Notes Introduction to Go 1.9 Changes to the language Ports ppc64x requires POWER8 Fre ...

  10. python try详细说明(python的异常捕捉模块)

    #自己常用 try: pass except Exception as e: print("break for :"+str(e)) # 划重点: 1. 正常执行try情况 pri ...