前言

显然qtree系列都是树链剖分辣

发现自己没有专门整理过树链剖分耶

辣么就把这篇博客魔改成树链剖分好辣(貌似除了树剖也没什么好写的)

正文

废话了辣么多终于开始了

一.树剖怎么写鸭

二.树剖有什么用鸭

三.qtree3题解

树剖怎么写鸭

树剖,顾名思义就是把树 剖成一条一条的东西,然后把一棵树搞成一个序列。
咋剖?
对于树上的每个节点,我们定义它的儿子中,有着最大子树的儿子就是重儿子(因为它画出来显得比较重),然后我们从根节点开始,一直走重儿子,就走出来一条重链。那些不在重链上的边怎么办呢?我们给他们起个名字,叫轻边。

处理重儿子

  1. void dfs1(int u,int fa)
  2. {
  3. dep[u]=dep[fa]+1;//这是深度
  4. par[u]=fa;
  5. sz[u]=1;
  6. for(int e=head[u];e;e=ed[e].nxt)
  7. {
  8. int v=ed[e].to;
  9. if(to==fa)continue;
  10. dfs(v,u);
  11. sz[u]+=sz[v];
  12. if(sz[son[now]]<sz[v])son[now]=v;
  13. }
  14. return;
  15. }

我们要把树搞成一个序列,所以要给每个节点一个编号dfn。dfn就是dfs序,dfn[i]也就是节点i是在dfs时第几个被遍历到了。在dfs时,先走重儿子,再走其他儿子。这样所有重儿子的dfn就是连续的,方便后面进行操作(why?)

emm蒟个栗子



其中加粗的点构成一条重链(最下面的点也可以是8,9)

搞重链和轻边

  1. void dfs2(int u,int fa)
  2. {
  3. cnt++;
  4. dfn[u]=cnt;
  5. id[cnt]=u;//记录dfn值对应的原来的编号
  6. if(son[fa]==u)top[u]=top[fa];
  7. else top[u]=u;
  8. if(!son[u])return ;
  9. dfs2(son[u],u);
  10. for(int e=head[u];e;e=ed[e].nxt)
  11. {
  12. int v=ed[e].to;
  13. if(v!=fa&&v!=son[u])
  14. dfs2(v,u);
  15. }
  16. }

辣么我们搞这个重链到底有什么用呢

良(du)心(liu)出题人肯定会搞一些树上的询问对不对?树上的询问一般都要涉及两个点之间的路径对不对?要搞路径就要找lca对不对?这时候就要用到重链和轻边了。

对于每一条链都会有一个链的顶部(一条轻边自己就是一条链),我们可以一条链一条链的往上跳,也就是每次都跳到当前所在链的顶部再往上一个点(以便找下一个顶部)。这样,当再跳一步,两个点所在链的顶部相同时,就说明lca在这条链中。

再蒟个栗子



现在我们要求6和8的lca

图中加粗的点是重儿子

6所处的链的顶端是1,8的顶端是10

这里我们先跳顶端比较深的那个点,也就是跳8

如果跳这一步:



这时候发现2和6的顶端是一样的,说明6和8的lca肯定在1->2->4->6这条链中

那么2和6中靠近根的点就是lca,在这里就是2。

找到了lca之后呢?再从lca往下跑?

不,不需要这么麻烦。

我们在上面处理dfn的时候,处于同一条重链上的点的dfn是连续的,也就是构成了一个区间。这个区间里的信息可以在线段树上维护。所以我们在找lca的时候,同时将这段区间的信息统计到答案里面。所以我们就切掉了查询操作。

查询代码(以求点权之和为例)

  1. int sum(int x,int y)
  2. {
  3. int ans=0;
  4. while(top[x]^top[y])//top[x]!=top[y]
  5. {
  6. if(dep[top[x]]<dep[top[y]])swap(x,y);
  7. ans+=query(1,1,n,dfn[top[x]],dfn[x]);//query就是线段树的query辣
  8. x=par[top[x]];
  9. }
  10. if(dep[x]>dep[y])swap(x,y);
  11. ans+=query(1,1,n,dfn[x],dfn[y]);
  12. return ans;
  13. }

什么?线段树怎么写?点这里qwq至于线段树维护什么因题而异,这里就不多说辣。

树剖是个什么玩意&有什么用

树剖可以对树进行操作,然后把树搞成一个区间,再加上线段树等数据结构的辅助,从而让我们的暴力跑的更快(港真树链剖分其实是一个优化暴力)

经典类型(可能以后会补充):原本序列上的操作被duliu出题人搞到了树上;给出的图是一棵树而且还会神烦的改边权

qtree3的题解

[传送](https://www.luogu.org/problem/P4116)
![](https://img2018.cnblogs.com/blog/1605842/201909/1605842-20190918163330140-934541195.png)
![](https://img2018.cnblogs.com/blog/1605842/201909/1605842-20190918163422985-1401214284.png)

其实qtree系列的难点在于我们要在线段树上维护什么。

对于这道题,我们要在线段树上维护什么呢?

当然是当前节点代表的区间中第一个黑点的dfn值辣。(初始值全部为inf)

然后结合线段树和树剖的板子这道题就写完了

一个大小坑:

我们在树剖的时候建的是双向边,所以注意#####数组要开到2e5!!!!!

鉴于luogu是个神奇的网站,不开2e5的边的评测结果是这样的:

emmmm当时真的以为是query无限递归什么的然后愣是WA了一个月没调出来,某天吃饭的时候突然醒悟边tm开小了

Code:

  1. #include<iostream>
  2. #include<queue>
  3. #include<cstdio>
  4. #include<algorithm>
  5. #include<cstring>
  6. #include<cmath>
  7. #define pa pair<int,int>
  8. typedef long long ll;
  9. using namespace std;
  10. inline int read()
  11. {
  12. char ch=getchar();
  13. int x=0;bool f=0;
  14. while(ch<'0'||ch>'9')
  15. {
  16. if(ch=='-')f=1;
  17. ch=getchar();
  18. }
  19. while(ch>='0'&&ch<='9')
  20. {
  21. x=(x<<3)+(x<<1)+(ch^48);
  22. ch=getchar();
  23. }
  24. return f?-x:x;
  25. }
  26. const int inf=21474836;
  27. int fir[300009],dfn[100009],son[100009],par[100009],top[100009];
  28. int n,q,dep[100009],cnt,head[100009],sz[100009];
  29. int tim,idx[100009];
  30. struct E{
  31. int to,nxt;
  32. }ed[300009];
  33. void add(int fr,int to)
  34. {
  35. cnt++;
  36. ed[cnt].to=to;
  37. ed[cnt].nxt=head[fr];
  38. head[fr]=cnt;
  39. }
  40. void dfs1(int now,int fa)
  41. {
  42. dep[now]=dep[fa]+1;
  43. par[now]=fa;
  44. sz[now]=1;
  45. for(int e=head[now];e;e=ed[e].nxt)
  46. {
  47. int v=ed[e].to;
  48. if(v==fa)continue;
  49. dfs1(v,now);
  50. sz[now]+=sz[v];
  51. if(sz[v]>sz[son[now]])
  52. son[now]=v;
  53. }
  54. return ;
  55. }
  56. void dfs2(int now,int fa)
  57. {
  58. ++tim;
  59. dfn[now]=tim;
  60. idx[tim]=now;
  61. if(son[fa]==now)
  62. top[now]=top[fa];
  63. else top[now]=now;
  64. if(!son[now]) return;
  65. dfs2(son[now],now);
  66. for(int e=head[now];e;e=ed[e].nxt)
  67. {
  68. int v=ed[e].to;
  69. if(v!=son[now]&&v!=fa)
  70. dfs2(v,now);
  71. }
  72. // printf("dfn[%d]=%d\n",now,dfn[now]);
  73. }
  74. void build(int k,int l,int r)
  75. {
  76. if(l==r)
  77. {
  78. fir[k]=inf;
  79. return ;
  80. }
  81. int mid=(l+r)>>1;
  82. build(k<<1,l,mid);
  83. build(k<<1|1,mid+1,r);
  84. fir[k]=inf;
  85. }
  86. void chg(int k,int l,int r,const int &v)
  87. {
  88. if(l==r)
  89. {
  90. if(fir[k]==inf) fir[k]=l;
  91. else fir[k]=inf;
  92. return ;
  93. }
  94. int mid=(l+r)>>1;
  95. if(v<=mid) chg(k<<1,l,mid,v);
  96. else chg(k<<1|1,mid+1,r,v);
  97. fir[k]=min(fir[k<<1],fir[k<<1|1]);
  98. }
  99. int query(int k,int l,int r,const int &x,const int &y)
  100. {
  101. if(x<=l&&r<=y)
  102. {
  103. return fir[k];
  104. }
  105. int mid=(l+r)>>1;
  106. int rtn=inf;
  107. if(x<=mid) rtn=min(rtn,query(k<<1,l,mid,x,y));
  108. if(mid<y) rtn=min(rtn,query(k<<1|1,mid+1,r,x,y));
  109. return rtn;
  110. }
  111. int qfir(int x)
  112. {
  113. int y=1;
  114. int rtn=inf;
  115. while(top[y]^top[x])
  116. {
  117. if(dep[top[x]]<dep[top[y]])swap(x,y);
  118. rtn=min(rtn,query(1,1,n,dfn[top[x]],dfn[x]));
  119. x=par[top[x]];
  120. }
  121. if(dep[x]>dep[y])swap(x,y);
  122. rtn=min(rtn,query(1,1,n,dfn[x],dfn[y]));
  123. return rtn;
  124. }
  125. int main()
  126. {
  127. n=read();q=read();
  128. for(int i=1;i<n;i++)
  129. {
  130. int fr=read(),to=read();
  131. add(fr,to);
  132. add(to,fr);
  133. }
  134. dfs1(1,0);
  135. dfs2(1,0);
  136. build(1,1,n);
  137. for(int i=1;i<=q;i++)
  138. {
  139. int cz=read(),v=read();
  140. if(cz==0)
  141. chg(1,1,n,dfn[v]);
  142. else
  143. {
  144. int ans=qfir(v);
  145. printf("%d\n",((ans==inf)?-1:idx[ans]));
  146. }
  147. }
  148. }

好了在结尾安利一道树剖模板题

树链剖分&咕咕咕了好久好久的qtree3的更多相关文章

  1. bzoj3631树链剖分

    虽然是水题1A的感觉太爽了O(∩_∩)O~ 题意相当于n-1次树上路径上每个点权值+1,最后问每个点的权值 本来想写线段树,写好了change打算框架打完了再来补,结果打完发现只是区间加和单点查 前缀 ...

  2. HDU 3966 & POJ 3237 & HYSBZ 2243 树链剖分

    树链剖分是一个很固定的套路 一般用来解决树上两点之间的路径更改与查询 思想是将一棵树分成不想交的几条链 并且由于dfs的顺序性 给每条链上的点或边标的号必定是连着的 那么每两个点之间的路径都可以拆成几 ...

  3. HDU 3966 Aragorn's Story 树链剖分+树状数组 或 树链剖分+线段树

    HDU 3966 Aragorn's Story 先把树剖成链,然后用树状数组维护: 讲真,研究了好久,还是没明白 树状数组这样实现"区间更新+单点查询"的原理... 神奇... ...

  4. BZOJ 1036:树的统计Count(树链剖分)

    http://www.lydsy.com/JudgeOnline/problem.php?id=1036 题意:中文题意. 思路:也是普通的树链剖分.唯一注意的点是在change函数中 while(t ...

  5. 【POJ3237】Tree(树链剖分+线段树)

    Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edg ...

  6. 【POJ3237】【树链剖分】Tree

    Description You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edg ...

  7. HDU 3966 Aragorn's Story 动态树 树链剖分

    Aragorn's Story Time Limit: 10000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  8. BZOJ 3083: 遥远的国度(树链剖分+DFS序)

    可以很显而易见的看出,修改就是树链剖分,而询问就是在dfs出的线段树里查询最小值,但由于这道题会修改根节点,所以在查询的时候需判断x是否为root的祖先,如果不是就直接做,是的话应该查询从1-st[y ...

  9. 【LuoguP3038/[USACO11DEC]牧草种植Grass Planting】树链剖分+树状数组【树状数组的区间修改与区间查询】

    模拟题,可以用树链剖分+线段树维护. 但是学了一个厉害的..树状数组的区间修改与区间查询.. 分割线里面的是转载的: ----------------------------------------- ...

随机推荐

  1. ivew组件上传图片文件的功能:

    解决的问题: 1.使用view的<Upload>组件实现图片文件的上传. 2.<Upload>组件action请求地址无法到自己写的后台. 3.前台base64的图片展示. 4 ...

  2. 六、while循环

    案例1: do while 循环  很少用到. for循环和while循环用的最多.

  3. git常用命令之log

       查看提交日志记录 基础命令:  git log commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon < ...

  4. 使用layer弹窗和layui表单做新增功能

    注释:代码参考http://blog.51cto.com/825272560/1891158,在其修改之上而来,在此感谢! 1.需求:使用layer在弹窗内完成新增,成功后提示并刷新页面(父页面,li ...

  5. 作为测试人员,不能不懂的adb命令和操作

    刚从web转到app测试,很多知识需要补充,记录一下   1.概念 其实我们口中所讲的adb是个泛指,这其中有两个工具——Fastboot和ADB   fastboot 快速启动,usb链接数据线的一 ...

  6. 如何修改wordpress博客默认管理员用户名称

    打开你的WordPress数据库,点击结构后面的SQL,输入下面一段命令执行 UPDATE wp_users SET user_login = '新用户名', user_nicename = '新用户 ...

  7. Mac下安装nginx并配置SSL实现Https的访问

    一.nginx安装 ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/insta ...

  8. Java 集合类库

    java类库的基本结构 Iterable public interface Iterable<T> 实现这个接口允许对象成为 "foreach" 语句的目标. 也就是说 ...

  9. Lambda表达式匿名类实现接口方法

    Lamb表达式匿名类实现接口方法 import java.util.ArrayList; public class HandlerDemo{ public static void main(Strin ...

  10. 17. ClustrixDB 日志管理

    ClustrixDB记录关于重要和有问题的查询的详细信息.这些日志有助于确定以下事项: 慢速查询 资源争用 SQL错误 读取意外数量行的查询 模式变化 全局变量的修改 集群的改变 默认情况下,查询日志 ...