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

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

特点是可以在$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. RPM包制作方法

    一.RPM介绍 RPM 前是Red Hat Package Manager 的缩写,本意是Red Hat 软件包管理,顾名思义是Red Hat 贡献出来的软件包管理:现在应为RPM Package M ...

  2. Package gtk+-3.0 was not found in the pkg-config search path

    问题描述: 在fedora21系统上通过rpmbuild构建fcitx的二进制包时出现以上错误,经老程序员指点:“是相应的开发包没有安装” 解决办法: yum installl gtk3-devel  ...

  3. Codeforces 691C. Exponential notation 模拟题

    C. Exponential notation time limit per test: 2 seconds memory limit per test:256 megabytes input: st ...

  4. java--多线程编程简介

    1.什么时候使用多线程编程 一个任务在正常情况下是按顺序执行的,但是如果当前任务里有多个相似进程块(例如for,while语句),我们就可以考虑把这些代码块抽出来并行运行,无需阻塞 2.实现多线程的几 ...

  5. 2018.06.30 BZOJ1857: [Scoi2010]传送带(三分套三分)

    1857: [Scoi2010]传送带 Time Limit: 1 Sec Memory Limit: 64 MB Description 在一个2维平面上有两条传送带,每一条传送带可以看成是一条线段 ...

  6. 2018.08.29 NOIP模拟 movie(状压dp/随机化贪心)

    [描述] 小石头喜欢看电影,选择有 N 部电影可供选择,每一部电影会在一天的不同时段播 放.他希望连续看 L 分钟的电影.因为电影院是他家开的,所以他可以在一部电影播放过程中任何时间进入或退出,当然他 ...

  7. hdu-1129(模拟题)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1129 注意:c_code[i]=(p_code[i*k%n]-i)%28; #include<i ...

  8. java.lang.ExceptionInInitializerError Caused by: org.hibernate.InvalidMappingException: Unable to read XML

    此错误是说无法读取你的xml文档,于是我们就该去更改xml文档,因为我是自动生成的,所以我找了一份之前手写的,发现是dtd错了,把之前的dtd拷贝过来之后程序就测试通过了

  9. Linux将程序添加到服务的方法(通用)

    一:咱们通过这篇文章来演示怎么将某个程序作为服务(就类似Windows服务可以开机自动启动),这里以tomcat为例,已经亲测过: 二:步骤(最好用root用户来做这种事情,切换root用户记得su ...

  10. csdn获得积分

    常规方式获取可用分 1.每天只要回复就可以获得10个可用分.注:回复后的第2天发放. 2.每周回复量大于10个帖子,将获得30可用分.注:下一周的周二发放. 3.本周获得技术专家分30分以上,将获得4 ...