dsu on tree的本质是树上的启发式合并,它利用启发式合并的思想,可以将O(N^2)的暴力优化成O(NlogN),用于不带修改的子树信息查询。

  具体如何实现呢?对于一个节点,继承它重儿子的信息,轻儿子直接dfs统计,更新完本节点的答案后,再dfs一次清除轻儿子的信息,相当于一个启发式合并的过程,因为一次合并会使得被遍历的子树变大一倍,所以一棵子树最多遍历logn次,也就是一个点最多被遍历logn次,于是最劣复杂度为O(NlogN)。

  dsu on tree的具体流程

    dfs计算轻儿子的答案,(有时候也需要继承轻儿子的答案)

    dfs计算重儿子的答案,继承重儿子的答案(有时候不需要)

    dfs计算轻儿子的贡献

    更新当前结点的答案

    dfs删去轻儿子的贡献

模板代码:

void dfs1(int x)
{
size[x]=;
for(int i=last[x];i;i=e[i].pre)
dfs1(e[i].too),size[x]+=size[e[i].too],size[e[i].too]>size[son[x]]&&(son[x]=e[i].too);
}
void update(int x,int fa,int delta)
{
// 更新信息
for(int i=last[x];i;i=e[i].pre)
if(e[i].too!=fa&&e[i].too!=skip)update(e[i].too,x,delta);
}
void dfs2(int x,int fa,bool heavy)
{
for(int i=last[x];i;i=e[i].pre)
if(e[i].too!=fa&&e[i].too!=son[x])dfs2(e[i].too,x,);
if(son[x])dfs(son[x],x,),skip=son[x],ans[x]=ans[son[x]];//有时不需要继承
update(x,fa,);skip=;
ans[x]+=....//有时在update里更新答案
if(!heavy)update(x,fa,-);
}

  例1 CF600E

  题目大意:统计每一个子树里众数的和(可以有多个众数)

  记录每种颜色的出现次数,然后就是模板题了...

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=,inf=1e9;
struct poi{int too,pre;}e[maxn];
int n,x,y,tot,zs,skip;
int last[maxn],col[maxn],cnt[maxn],son[maxn],size[maxn];
ll sum,ans[maxn];
inline void read(int &k)
{
int f=;k=;char c=getchar();
while(c<''||c>'')c=='-'&&(f=-),c=getchar();
while(c<=''&&c>='')k=k*+c-'',c=getchar();
k*=f;
}
inline void add(int x,int y){e[++tot].too=y;e[tot].pre=last[x];last[x]=tot;}
void dfs1(int x,int fa)
{
size[x]=;
for(int i=last[x],too;i;i=e[i].pre)if((too=e[i].too)!=fa)
dfs1(too,x),size[x]+=size[too],size[too]>size[son[x]]&&(son[x]=too);
}
void update(int x,int fa,int delta)
{
cnt[col[x]]+=delta;
if(delta>&&cnt[col[x]]>zs)zs=cnt[col[x]],sum=col[x];
else if(delta>&&cnt[col[x]]==zs)sum+=col[x];
for(int i=last[x];i;i=e[i].pre)
if(e[i].too!=fa&&e[i].too!=skip)
update(e[i].too,x,delta);
}
void dfs(int x,int fa,bool heavy)
{
for(int i=last[x];i;i=e[i].pre)
if(e[i].too!=fa&&e[i].too!=son[x])dfs(e[i].too,x,);
if(son[x])dfs(son[x],x,),skip=son[x];
update(x,fa,);ans[x]=sum;skip=;
if(!heavy)update(x,fa,-),zs=sum=;
}
int main()
{
read(n);
for(int i=;i<=n;i++)read(col[i]);
for(int i=;i<n;i++)read(x),read(y),add(x,y),add(y,x);
dfs1(,);dfs(,,);
for(int i=;i<=n;i++)printf("%lld ",ans[i]);
}

  例2 CF741D

  题目大意:每一条边有一个字符,求每一个子树里最长的路径使得路径上的字符经过重新排列可以成为回文串

  这题有点像上场atcoder的D题...因为一个字符串经过重新排列可以成为回文串当且仅当每种颜色的出现次数为偶数或者只有一种颜色的出现次数为奇数,于是可以想到利用异或来判断。将每一种字符映射到二进制上的一位(1<<(ch-'a)),预处理出点到根节点的路径上的异或值,那么判断两个节点的路径上是否可以经过重新排列成为回文串就是st[x]^st[y]是不是0或者2的幂。

  所以这题我们只需要记录状态为i的最深深度,然后就又是模板题了。

  注意状态为i的如果没有要设为-inf,否则会GG (调了一天T T

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=,inf=1e9;
struct poi{int too,dis,pre;}e[maxn];
int n,x,y,tot,skip,sum;
int st[maxn],col[maxn],size[maxn],dep[maxn],son[maxn],mx[<<],last[maxn],ans[maxn];
char ch[];
inline void read(int &k)
{
int f=;k=;char c=getchar();
while(c<''||c>'')c=='-'&&(f=-),c=getchar();
while(c<=''&&c>='')k=k*+c-'',c=getchar();
k*=f;
}
inline void add(int x,int y,int z){e[++tot].too=y; e[tot].dis=z; e[tot].pre=last[x]; last[x]=tot;}
void dfs1(int x,int fa)
{
size[x]=;
for(int i=last[x],too;i;i=e[i].pre) if((too=e[i].too)!=fa)
{
dep[too]=dep[x]+;
st[too]=st[x]^(<<e[i].dis);
dfs1(too,x);
size[x]+=size[too];
if(size[too]>size[son[x]]) son[x]=too;
}
}
void Count(int x,int fa,int f)
{
sum=max(sum,mx[st[x]]+dep[x]-*dep[f]);
for(int i=;i<;i++) sum=max(sum, mx[st[x]^(<<i)]+dep[x]-*dep[f]);
if(x==f)return;
for(int i=last[x],too;i;i=e[i].pre)
if((too=e[i].too)!=fa && too!=skip) Count(too,x,f);
}
void update(int x,int fa,int delta)
{
if(delta>) mx[st[x]]=max(mx[st[x]],dep[x]);
else mx[st[x]]=-inf;
for(int i=last[x],too;i;i=e[i].pre)
if((too=e[i].too)!=fa && too!=skip) update(too,x,delta);
}
void dfs2(int x,int fa,bool heavy)
{
for(int i=last[x],too;i;i=e[i].pre)
if((too=e[i].too)!=fa && too!=son[x])
dfs2(too,x,), ans[x]=max(ans[x],ans[too]);
if(son[x]) dfs2(son[x],x,), skip=son[x], ans[x]=max(ans[x],ans[son[x]]);
sum=; Count(x,fa,x); mx[st[x]]=max(mx[st[x]],dep[x]);
for(int i=last[x],too;i;i=e[i].pre)
if((too=e[i].too)!=fa && too!=skip)
Count(too,x,x), update(too,x,);
ans[x]=max(ans[x],sum); skip=;
if(!heavy) update(x,fa,-);
}
int main()
{
read(n); for(int i=;i<(<<);i++) mx[i]=-inf;
for(int i=;i<=n;i++) read(x), scanf("%s",ch), y=ch[]-'a',add(x,i,y);
dfs1(,); dfs2(,,);
for(int i=;i<=n;i++) printf("%d ",ans[i]);
}

  例3 NOIP模拟赛10.23 T2

【算法】dsu on tree初探的更多相关文章

  1. [算法学习] dsu on tree

    简介 dsu on tree跟dsu没有关系,但是dsu on tree借鉴了dsu的启发式合并的思想. 它是用来解决一类树上的询问问题,一般这种问题有以下特征: \(1.\)只有对子树的查询: \( ...

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

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

  3. dsu on tree入门

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

  4. [学习笔记]Dsu On Tree

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

  5. dsu on tree题表

    dsu on tree,又名树上启发式合并.重链剖分,是一类十分实用的trick,它常常可以作为一些正解的替代算法: 1.DFS序+线段树/主席树/线段树合并 2.对DFS序分块的树上莫队 3.长链剖 ...

  6. DSU on Tree浅谈

    DSU on tree 在之前的一次比赛中,学长向我们讲了了这样一个神奇的思想:DSU on tree(树上启发式合并),看上去就非常厉害--但实际上是非常暴力的一种做法;不过暴力只是看上去暴力,它在 ...

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

    [题意]给定n个点的树,每条边有一个小写字母a~v,求每棵子树内的最长回文路径,回文路径定义为路径上所有字母存在一种排列为回文串.n<=5*10^5. [算法]dsu on tree [题解]这 ...

  8. 【CodeForces】600 E. Lomsat gelral (dsu on tree)

    [题目]E. Lomsat gelral [题意]给定n个点的树,1为根,每个点有一种颜色ci,一种颜色占领一棵子树当且仅当子树内没有颜色的出现次数超过它,求n个答案——每棵子树的占领颜色的编号和Σc ...

  9. dsu on tree总结

    dsu on tree 树上启发式合并.我并不知道为什么要叫做这个名字... 干什么的 可以在\(O(n\log n)\)的时间内完成对子树信息的询问,可横向对比把树按\(dfs\)序转成序列问题的\ ...

随机推荐

  1. Java连载84-Collection的常用方法、迭代器

    一.Collections的常用方法介绍 1.承接上次连载,先介绍几个简单的常用方法 package com.bjpowernode.java_learning; import java.util.* ...

  2. VS+QT创建一个OpenCV应用

    1.选择:文件->新建->项目->搜索“QT”->Qt GUI Application,输入工程名,下一步…进行新建. 选择依赖的模块:最基础的QtCore.QtGui.QtW ...

  3. 《深入理解Java虚拟机》读书笔记十

    第十一章  晚期(运行期)优化 1.HotSpot虚拟机内的即时编译 解释器与编译器: 许多Java虚拟机的执行引擎在执行Java代码的时候都有解释执行(通过解释器执行)和编译执行(通过即时编译器产生 ...

  4. 《NVMe-over-Fabrics-1_0a-2018.07.23-Ratified》阅读笔记(4)-- Controller Architecture

    4 Controller架构 NVMe over Fabrics使用与NVMe基础规格说明书中定义相同的controller架构.这包括主机和controller之间使用SQ提交队列和CQ完成队列来执 ...

  5. JAVA 注解教程(四)Java 预置的注解

    @Deprecated 这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到.编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法.过时的类.过时的成员变 ...

  6. JAVA 注解教程(一)简单介绍

    相关网址 https://blog.csdn.net/briblue/article/details/73824058 --- 已经整理成笔记 https://blog.csdn.net/bao199 ...

  7. WPF学习笔记二之依赖属性

    1.快捷生成依赖属性:propdp然后按两次tab键 2.应用场景:自定义控件 什么是依赖属性:依赖属性自己没有值,通过依赖别人(如Binding)来获得值. 依赖属性为什么会出现:控件常用字段有限, ...

  8. Action路径问题

    上网搜了一下,先给个解决方案,贴个图保存,后面再专门写一写总结.

  9. CAN总线电平(隐性与显性)

            CAN2.0B规范定义了两种互补的逻辑数值:显性和隐性.同时传送显性和隐性位时,总线呈现显性状态:同时传送显性状态位时,总线呈现显性状态:同时传送隐性状态位时,总线呈现隐性状态.显性数 ...

  10. AcWing 9. 分组背包问题

    #include <iostream> #include <algorithm> using namespace std; ; int n, m; int v[N][N], w ...