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的答案。

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

 #include<cstdio>
#include<algorithm>
#include<iostream>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
typedef long long ll;
using namespace std; const int N=;
int n,u,v,cnt,tot[N],mx,col[N],sz[N],son[N],h[N],to[N],nxt[N];
ll ans[N],sm;
bool skip[N]; void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } void get(int x,int fa){
sz[x]=;
For(i,x) if ((k=to[i])!=fa){
get(k,x); sz[x]+=sz[k];
if (sz[k]>sz[son[x]]) son[x]=k;
}
} void dfs(int x,int fa,int op){
tot[col[x]]+=op;
if (op> && tot[col[x]]>=mx){
if (tot[col[x]]>mx) sm=,mx=tot[col[x]];
sm+=col[x];
}
For(i,x) if ((k=to[i])!=fa && !skip[k]) dfs(k,x,op);
} void work(int x,int fa,bool cl){
For(i,x) if ((k=to[i])!=fa && k!=son[x]) work(k,x,);
if (son[x]) work(son[x],x,),skip[son[x]]=;
dfs(x,fa,); ans[x]=sm; skip[son[x]]=;
if (cl) dfs(x,fa,-),mx=sm=;
} int main(){
scanf("%d",&n);
rep(i,,n) scanf("%d",&col[i]);
rep(i,,n) scanf("%d%d",&u,&v),add(u,v),add(v,u);
get(,); work(,,);
rep(i,,n) cout<<ans[i]<<' ';
return ;
}

CF600E

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

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

 #include<cstdio>
#include<vector>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
using namespace std; const int N=;
char s[N];
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];
struct P{ int x,y; };
vector<P>ve[N]; void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } void dfs(int x){
d[x]=d[fa[x]]+; sz[x]=;
For(i,x){
dfs(k=to[i]); sz[x]+=sz[k];
if (sz[k]>sz[son[x]]) son[x]=k;
}
} void get(int x){
a[d[x]]^=<<(s[x]-'a');
For(i,x) if ((k=to[i])!=skip) get(k);
} void work(int x,int op){
For(i,x) if ((k=to[i])!=son[x]) work(k,);
if (son[x]) work(son[x],),skip=son[x];
get(x); skip=; int ed=ve[x].size()-;
rep(i,,ed) ans[ve[x][i].x]=__builtin_popcount(a[ve[x][i].y])<=;
if (op) get(x);
} int main(){
freopen("570D.in","r",stdin);
freopen("570D.out","w",stdout);
scanf("%d%d",&n,&m);
rep(i,,n) scanf("%d",&fa[i]),add(fa[i],i);
rep(i,,n) scanf(" %c",&s[i]);
rep(i,,m) scanf("%d%d",&x,&y),ve[x].push_back((P){i,y});
dfs(); work(,);
rep(i,,m) if (ans[i]) puts("Yes"); else puts("No");
return ;
}

CF570D

例三:

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

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

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

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

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

 #include<set>
#include<map>
#include<string>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<iostream>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
using namespace std; const int N=;
string s;
map<string,int>mp;
int n,Q,x,y,tot,skip,cnt,rts,id[N],fa[N],rt[N],ans[N],sz[N];
int son[N],d[N],h[N],to[N<<],nxt[N<<];
set<int>S[N];
struct P{ int x,y; };
vector<P>ve[N];
void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } void dfs(int x){
d[x]=d[fa[x]]+; sz[x]=;
For(i,x){
dfs(k=to[i]); sz[x]+=sz[k];
if (sz[k]>sz[son[x]]) son[x]=k;
}
} void get(int x,int op){
if (op) S[d[x]].insert(id[x]); else S[d[x]].erase(id[x]);
For(i,x) if ((k=to[i])!=skip) get(k,op);
} void work(int x,int op){
For(i,x) if ((k=to[i])!=son[x]) work(k,);
if (son[x]) work(son[x],),skip=son[x];
get(x,); skip=; int ed=ve[x].size()-;
rep(i,,ed) ans[ve[x][i].x]=S[d[x]+ve[x][i].y].size();
if (op) get(x,);
} int main(){
scanf("%d",&n);
rep(i,,n){
cin>>s>>fa[i];
if (mp.find(s)!=mp.end()) id[i]=mp[s]; else mp[s]=id[i]=++tot;
if (!fa[i]) rt[++rts]=i; else add(fa[i],i);
}
scanf("%d",&Q);
rep(i,,Q) scanf("%d%d",&x,&y),ve[x].push_back((P){i,y});
rep(i,,rts) dfs(rt[i]),work(rt[i],);
rep(i,,Q) printf("%d\n",ans[i]);
return ;
}

CF246E

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

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

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

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

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

 #include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
#define For(i,x) for (int i=h[x],k; i; i=nxt[i])
using namespace std; const int N=,inf=1e9;
char s[N];
int n,cnt,d[N],fa[N],ans[N],w[N],sz[N],son[N],p[<<],h[N],to[N<<],nxt[N<<];
void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } void dfs(int x){
d[x]=d[fa[x]]+; sz[x]=;
For(i,x){
k=to[i]; w[k]=w[x]^(<<(s[k]-'a')); dfs(k); sz[x]+=sz[k];
if (sz[k]>sz[son[x]]) son[x]=k;
}
} void get(int rt,int x){
ans[rt]=max(ans[rt],p[w[x]]+d[x]-*d[rt]);
rep(i,,) ans[rt]=max(ans[rt],p[w[x]^(<<i)]+d[x]-*d[rt]);
For(i,x) get(rt,k=to[i]);
} void get2(int x){ p[w[x]]=max(p[w[x]],d[x]); For(i,x) get2(k=to[i]); } void del(int x){ p[w[x]]=-inf; For(i,x) del(k=to[i]); } void work(int x,int op){
For(i,x) if ((k=to[i])!=son[x]) work(k,),ans[x]=max(ans[x],ans[k]);
if (son[x]) work(son[x],),ans[x]=max(ans[x],ans[son[x]]);
For(i,x) if ((k=to[i])!=son[x]) get(x,k),get2(k);
ans[x]=max(ans[x],p[w[x]]-d[x]);
rep(i,,) ans[x]=max(ans[x],p[w[x]^(<<i)]-d[x]);
p[w[x]]=max(p[w[x]],d[x]);
if (op) del(x);
} int main(){
freopen("741D.in","r",stdin);
freopen("741D.out","w",stdout);
scanf("%d",&n);
rep(i,,n) scanf("%d %c",&fa[i],&s[i]),add(fa[i],i);
rep(i,,(<<)-) p[i]=-inf;
dfs(); work(,);
rep(i,,n) printf("%d ",ans[i]);
return ;
}

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. vim编辑器基本操作介绍

    vim编辑器基本操作介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 可能很多小伙伴都听说过vi编辑器或是vim编辑器.它们是Unix和Linux世界最流行的编辑器之一,他们的特 ...

  2. jQuery1.11源码分析(3)-----Sizzle源码中的浏览器兼容性检测和处理[原创]

    上一章讲了正则表达式,这一章继续我们的前菜,浏览器兼容性处理. 先介绍一个简单的沙盒测试函数. /** * Support testing using an element * @param {Fun ...

  3. 一个很实用的css3兼容工具很多属性可以兼容到IE6

    当你看到这样的效果图是不是已经崩溃了 css3没出来之前大部分人基本都是用图片的方式拼出来的 腾讯邮箱就是这么做的 然后你想和设计说换直角吧.我用图片的好烦的感觉!而且我们还要兼容到ie6 她和你说别 ...

  4. [译]使用chage来管理Linux密码过期时间的七个例子

    本文译自 7 Examples to Manage Linux Password Expiration and Aging Using chage 本文主要介绍命令chage的使用,译文会对原文内容会 ...

  5. redhat本地yum源配置

    /dev/sr0是光驱的设备名,/dev/cdrom代表光驱 /dev/sr0 与/dev/cdrom /dev/cdrom 只是一个到sr0的符号链接 mount /dev/sr0   /mnt   ...

  6. 【Alsa】播放声音和录音详细流程

    linux中,无论是oss还是alsa体系,录音和放音的数据流必须分析清楚.先分析alsa驱动层,然后关联到alsa库层和应用层. 二,链接分析: 1)链路一 usr/src/linux-source ...

  7. OpenLayers 3 之 地图图层数据来源(ol.source)详解

    原文地址 source 是 Layer 的重要组成部分,表示图层的来源,也就是服务地址.除了在构造函数中制定外,可以使用 layer.setSource(source) 稍后指定.一.包含的类型 ol ...

  8. elasticsearch安装ik分词器(非极速版)

    1.下载下载地址为: https://github.com/medcl/elasticsearch-analysis-ik 2.解压把下载的 elasticsearch-analysis-ik.zip ...

  9. ecplise里的run as里只有run configurations是怎么回事?

    一.没有main方法 二.main方法所在的类不是在与文件名同名的类中

  10. Demo005 小学四则运算自动生成程序

    目录 小学四则运算自动生成程序 0.传送门 1.题目要求 2.功能实现 2.1 总体设计 2.2 用户欢迎界面 2.3 用户功能界面 2.4 屏幕输出 2.5 文本输出 2.6 获取时间 2.7 用户 ...