我也不知道为啥这要起这名,完完全全没看到并查集的影子啊……

实际上原理就是一个树上的启发式合并。

特点是可以在$O(nlogn)$的时间复杂度内完成对无修改的子树的统计,复杂度优于莫队算法。

局限性也很明显:1.不能支持修改  2.只能支持子树统计,不能链上统计。(链上统计你不能直接树剖吗?)

那么它是怎么实现的呢?首先有一个例子:
树上每个节点都有一个颜色(那么一定是蓝色),

求每个节点的子树上有多少颜色为k的节点。(每个节点的k不一定相同)

$O(n^2)$的算法非常好想,以每个点为起点dfs一下就没了。

当然也有不那么暴力的做法,dfs序一下再主席树或者莫队随便搞搞也行。

那么我们先看看暴力是怎么做的。

每次统计x节点前,暴力将x的子树的贡献加入,统计结束后,再暴力删除贡献,消除影响。

我们发现这有很多无用的删除操作,考虑优化?

那么我们怎么用dsu上树优雅的解决这个问题呢?我们想到了树链剖分(轻重链剖分)。

具体的做法是,我们先统计一个点的轻儿子,再把它的影响消除。再统计重儿子,此时不必消除影响。

为了完成统计,最后再统计一遍轻儿子。

可以这么考虑:只有dfs到轻边时,才会将轻边的子树中合并到上一级的重链,

树链剖分将一棵树分割成了不超过$logn$条重链。
每一个节点最多向上合并$logn$次,单次修改复杂度$O(1)$。
所以整体复杂度是$O(nlogn)$的。

所以大概的模版是这样的:

 void dfs2(int u,int f,int k){
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==f||v==wson[u])continue;
dfs2(v,u,);
}
if(wson[u])dfs(wson[u],u,),now=wson[u];
calc(u,f,);
now=;ans[u]=sum;
if(k==)calc(u,f,-),sumv=,maxv=;
}

下面是两道烂大街的例题:

1. Lomsat gelral(cf600E)

n个点的有根树,以1为根,每个点有一种颜色。我们称一种颜色占领了一个子树当且仅当没有其他颜色在这个子树中出现得比它多。求占领每个子树的所有颜色之和。

就是刚才的裸题啊。

 #include<bits/stdc++.h>
#define N 700010
using namespace std;
struct Edge{int u,v,next;}G[*N];
typedef long long ll;
int n,c[N],val[N],size[N],wson[N],fa[N];
ll ans[N];
int head[*N],tot=;
void addedge(int u,int v){
G[++tot].u=u;G[tot].v=v;G[tot].next=head[u];head[u]=tot;
G[++tot].u=v;G[tot].v=u;G[tot].next=head[v];head[v]=tot;
}
void dfs1(int u,int f=){
size[u]=;
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==f)continue;
if(v==f)continue;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[wson[u]])wson[u]=v;
}
}
bool vis[N];int maxv=;ll sum=;
void change(int u,int f,int k){
c[val[u]]+=k;
if(k>&&c[val[u]]>=maxv){
if(c[val[u]]>maxv)sum=,maxv=c[val[u]];
sum+=val[u];
}
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==f||vis[v])continue;
change(v,u,k);
}
}
void dfs2(int u,int f=,bool used=){
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==f||v==wson[u])continue;
dfs2(v,u);
}
if(wson[u])dfs2(wson[u],u,),vis[wson[u]]=;
change(u,f,);ans[u]=sum;
if(wson[u])vis[wson[u]]=;
if(!used)change(u,f,-),maxv=sum=;
}
inline int read(){
int f=,x=;char ch;
do{ch=getchar();if(ch=='-')f=-;}while(ch<''||ch>'');
do{x=x*+ch-'';ch=getchar();}while(ch>=''&&ch<='');
return f*x;
}
int main(){
n=read();
for(int i=;i<=n;i++)val[i]=read();
for(int i=;i<n;i++){
int u=read(),v=read();
addedge(u,v);
}
dfs1();dfs2();
for(int i=;i<=n;i++)printf("%I64d ",ans[i]);
}

当然这题也有不这么做的做法,随便从cf上粘了一个,大家自行意会……

 #include<bits/stdc++.h>
#define N 100005
using namespace std;
vector<int>a[N];map<int,int>S[N];
int F[N],id[N],c[N],n,i,x,y;
long long G[N],ans[N];
void work(int x,int y,int color){
if (y>F[x]) F[x]=y,G[x]=;
if (y==F[x]) G[x]+=color;
}
void Union(int &x,int y){
if (S[x].size()<S[y].size()) swap(x,y);
for (map<int,int>::iterator it=S[y].begin();it!=S[y].end();it++)
work(x,S[x][it->first]+=it->second,it->first);
}
void DFS(int x,int fa){
id[x]=x;S[x][c[x]]=;
F[x]=;G[x]=c[x];
for (int i=,y;i<a[x].size();i++)
if ((y=a[x][i])!=fa)
DFS(y,x),Union(id[x],id[y]);
ans[x]=G[id[x]];
}
int main(){
scanf("%d",&n);
for (i=;i<=n;i++)
scanf("%d",&c[i]);
for (i=;i<n;i++)
scanf("%d%d",&x,&y),
a[x].push_back(y),
a[y].push_back(x);
DFS(,);
for (i=;i<=n;i++)
printf("%I64d ",ans[i]);
}

例2: Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths(CF741D)

这题也很显然,如果重排后能形成回文串,那么出现奇数次的字符应该少于2个(即最多1个)如果只有a~v的话考虑把每个字符当做一个二进制位,把一个点i到根的路径异或值记为s[i],那么我们就是要对于每个x在子树中找到a和b,使得s[a]^s[b]为0或2的次幂,且dep[a]+dep[b]-dep[lca]*2最大。

 #include<bits/stdc++.h>
#define N 500005
using namespace std;
int size[N],head[*N],tot=,wson[N],s[N],f[*N],ans[N],d[N],a[N];
char c[N];
int maxv,n,inf;
struct Edge{int u,v,next;}G[*N];
void addedge(int u,int v){
G[++tot].u=u;G[tot].v=v;G[tot].next=head[u];head[u]=tot;
//G[++tot].u=v;G[tot].v=u;G[tot].next=head[v];head[v]=tot;
}
void dfs1(int u,int fa){
size[u]=;d[u]=d[fa]+;
if(u!=)s[u]=s[fa]^(<<a[u]);
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;
dfs1(v,u);
size[u]+=size[v];if(size[v]>size[wson[u]])wson[u]=v;
}
}
void calc(int rt,int u){
int now=s[u];
maxv=max(maxv,f[now]+d[u]-*d[rt]);
if((s[u]^s[rt])==)maxv=max(maxv,d[u]-d[rt]);
for(int i=;i<;i++){
now=(<<i)^s[u];
maxv=max(maxv,f[now]+d[u]-*d[rt]);
if((s[u]^s[rt])==(<<i))maxv=max(maxv,d[u]-d[rt]);
}
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;calc(rt,v);
}
}
void change(int u,int k){
if(k)f[s[u]]=max(f[s[u]],d[u]);
else f[s[u]]=inf;
for(int i=head[u];i;i=G[i].next)change(G[i].v,k);
}
void dfs2(int u,int k){
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==wson[u])continue;
dfs2(v,);
}
if(wson[u])dfs2(wson[u],);
maxv=;int now=s[u];
maxv=max(maxv,f[now]-d[u]);
for(int i=;i<;i++){
now=(<<i)^s[u];
maxv=max(maxv,f[now]-d[u]);
}
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==wson[u])continue;
calc(u,v);change(v,);
}
ans[u]=maxv;
if(!k){
for(int i=head[u];i;i=G[i].next)change(G[i].v,);
f[s[u]]=inf;
}else f[s[u]]=max(f[s[u]],d[u]);
}
void erase(int u){
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;erase(v);
ans[u]=max(ans[u],ans[v]);
}
}
int main(){
scanf("%d",&n);
for(int i=;i<=n;i++){
int u;scanf("%d %c\n",&u,&c[i]);
addedge(u,i);a[i]=c[i]-'a';
}
dfs1(,);
memset(f,,sizeof(f));inf=f[];dfs2(,);
erase();
for (int i=;i<=n;++i)printf("%d%c",ans[i]," \n"[i==n]);
}

大概是这样。

参考:

http://blog.csdn.net/qq_35392050/article/details/64537364

http://www.cnblogs.com/zzqsblog/p/6146916.html

【学习笔记】dsu on tree的更多相关文章

  1. [学习笔记]Dsu On Tree

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

  2. [学习笔记]dsu on a tree(如何远离线段树合并)

    https://www.zybuluo.com/ysner/note/1318613 背景 这玩意来源于一种有局限性的算法. 有一种广为人知的,树上离线维护子树信息的做法. (可以参照luogu360 ...

  3. 决策树学习笔记(Decision Tree)

    什么是决策树? 决策树是一种基本的分类与回归方法.其主要有点事模型具有可得性,分类速度快.学习时,利用训练数据,根据损失函数最小化原则建立决策树模型:预测时,对新数据,利用决策树模型进行分类. 决策树 ...

  4. [学习笔记] Uplift Decision Tree With KL Divergence

    Uplift Decision Tree With KL Divergence Intro Uplift model 我没找到一个合适的翻译,这方法主要应用是,探究用户在给予一定激励之后的表现,也就是 ...

  5. 【学习笔记】K-D tree 区域查询时间复杂度简易证明

    查询算法的流程 如果查询与当前结点的区域无交集,直接跳出. 如果查询将当前结点的区域包含,直接跳出并上传答案. 有交集但不包含,继续递归求解. K-D Tree 如何划分区域 可以借助下文图片理解. ...

  6. dsu on tree 树上启发式合并 学习笔记

    近几天跟着dreagonm大佬学习了\(dsu\ on\ tree\),来总结一下: \(dsu\ on\ tree\),也就是树上启发式合并,是用来处理一类离线的树上询问问题(比如子树内的颜色种数) ...

  7. 树上启发式合并(dsu on tree)学习笔记

    有丶难,学到自闭 参考的文章: zcysky:[学习笔记]dsu on tree Arpa:[Tutorial] Sack (dsu on tree) 先康一康模板题吧:CF 600E($Lomsat ...

  8. dsu on tree学习笔记

    前言 一次模拟赛的\(T3\):传送门 只会\(O(n^2)\)的我就\(gg\)了,并且对于题解提供的\(\text{dsu on tree}\)的做法一脸懵逼. 看网上的其他大佬写的笔记,我自己画 ...

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

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

  10. dsu on tree 学习笔记

    这是一个黑科技,考虑树链剖分后,每个点只会在轻重链之间转化\(log\)次. 考虑暴力是怎么写的,每次枚举一个点,再暴力把子树全部扫一边. \(dsu\ on\ tree.\)的思想就是保留重儿子不清 ...

随机推荐

  1. tomcat执行文件权限

    .当我在linux下某个目录执命令或者安装的时候通常会提示没有权限或者不可以操作,这时需要加权限 chmod /usr/local/tomcat/bin; 2关于LINUX权限(启动tomcat)-b ...

  2. php调用C#写的dll包

    1.转到需要注册的dll路径下 2.输入regasm命令+文件名 3问题解决

  3. vue cli+axios踩坑记录+拦截器使用,代理跨域proxy(更新)

    16319 1.首先axios不支持vue.use()方式声明使用,看了所有近乎相同的axios文档都没有提到这一点建议方式 在main.js中如下声明使用 import axios from 'ax ...

  4. centos 安装或更新最新版本软件包(git python etc)的方法 SCL IUS

    使用centos 经常发现官方提供的软件包版本过低,很多时候大家会选择下载源码自行编译,带来了很多麻烦. centos安装最新版本软件包,例如git,python等,可以通过红帽官方提供的softwa ...

  5. Monte carlo

    转载 http://blog.sciencenet.cn/blog-324394-292355.html 蒙特卡罗(Monte Carlo)方法,也称为计算机随机模拟方法,是一种基于"随机数 ...

  6. ORACLE实用函数之一 ratio_to_report的简单使用

    应用场景: 查询学生成绩级别(ABCDE)个人数和所占百分比(案列简单,勿喷). 表结构: create or replace table stu_grade( id varchar2(36), le ...

  7. 2018.09.24 codeforces 1053C. Putting Boxes Together(线段树)

    传送门 就是让你维护动态的区间带权中位数. 然而昨晚比赛时并没有调出来. 想找到带权中位数的中点可以二分(也可以直接在线段树上找). 也就是二分出第一个断点,使得断点左边的和恰好大于或等于断点右边的和 ...

  8. head first 设计模式文摘

    1 欢迎来到设计模式世界:设计模式入门 2 让你的对象知悉现况:观察者模式 3 装饰对象:装饰者模式 4 工厂模式:烘烤OO的精华 5 单件模式:独一无二的对象 6 命令模式:封装调用 7 适配器模式 ...

  9. RHEL6解决无法使用YUM源问题(转)

    RHEL的YUM源需要注册用户才能更新使用,由于CentOS和RHEL基本没有区别,并且CentOS已经被REHL收购.所以将RHEL的YUM源替换为CentOS即可.问题如下:[root@bogon ...

  10. 学习前端的菜鸡对JS 的classList理解

    classList 在早期的时候要添加,删除类 需要用className去获取,然后通过正则表达式去判断这个类是否存在. 代码上去会有点麻烦,现在有了classList 就方便了很多. ——————— ...