早先以为莫队是个顶有用的东西,不过好像树上莫队(不带修)被dsu碾压?

dsu one tree起源

dsu on tree是有人在cf上blog上首发的一种基于轻重链剖分的算法,然后好像由因为这个人后来在cf上办了场比赛出了道dsu on tree的裸题由此出名?

这个是原博客地址:http://codeforces.com/blog/entry/44351

大概思想就是一种树上启发式合并,利用轻重链剖分把重复计算的答案利用起来,从而把时间复杂度控制在$O(n log n)$(不过不能修改)。

注意dsu的一大特点:一次dsu之后是把整棵树的答案都处理出来,因此它更适合大量查询的情况。

算法流程

下面讲一下算法流程:

1.预处理树的结构,把$fa$,$son$,$tot$处理出来。具体操作参见树剖。

2.为了统计当前节点答案,先递归处理所有轻儿子。然后递归回当前节点时,其子树内除了重儿子都已经处理好答案了。

3.如果有重儿子,那么递归重儿子同时标记重儿子这个节点

4.现在子树信息都已经处理好了,考虑向上合并信息。我们把子树内所有点都统计颜色一遍(除了重儿子的家族)

5.现在子树信息传递好了

6.如果这个节点是轻儿子转移过来的,那么清除这颗子树所有信息(包括子树里的重儿子)

7.向上递归返回

 void color(int x, int c)    //把x的子树都统计一遍
{
cnt[a[x].col] += c;
if (c>&&mx <= cnt[a[x].col])
{
if (mx < cnt[a[x].col]) mx = cnt[a[x].col], sum = ;
sum += a[x].col;    //如果现在是
}
for (int i=; i<f[x].size(); i++)
if (f[x][i]!=a[x].fa&&!vis[f[x][i]])    //处理儿子节点
color(f[x][i], c);
}
void dfs2(int x, bool fl)      //dsu:x表示当前节点  fl表示当前节点是轻儿子还是重儿子
{
for (int i=; i<f[x].size(); i++)
if (f[x][i]!=a[x].son&&f[x][i]!=a[x].fa)
dfs2(f[x][i], );
if (a[x].son!=-) dfs2(a[x].son, ), vis[a[x].son] = ;
color(x, );  //c==1是统计答案;c==0是消除答案
ans[x] = sum;
if (a[x].son!=-) vis[a[x].son] = ;
if (!fl) color(x, -), mx = sum = ;
}

上面讲得似乎不适合初学者?……学的时候也看这些文字描述,不过还是有些疑惑。下面用一些图片来描述一下。

简单易懂的图片描述?

假设现在我们已经递归处理好了重儿子和轻儿子,现在打算向上传递到x节点。

我们的重儿子的颜色的信息是保存下来的,所以只需要再次添加轻儿子的颜色信息。

光光这样是不够的,可以想到如果出现这种情况呢?

现在x节点的颜色信息已经被确定了,并且在a统计时候信息并没有删除,好像这样会对b节点统计时候产生影响???

如果你这样想那么就跟我一样了。

注意到$dsu$的$dfs2$时候带了个$fl$参数表示当前节点是轻儿子还是重儿子。那么这个东西到底是干嘛用的呢?

这个a节点是由轻儿子转移过来的,因此它的$fl==0$。然而我们知道轻儿子是不保存信息的。

当a节点向上递归回去的时候,说明a节点的子树都已经统计好了。那么最后就有一个$color(x, -1)$操作,把x子树全部「清洗」掉去。

这样子按照轻重链剖分的顺序我们就能够利用大量重复的子树颜色信息,从而控制复杂度在$O(n log n)$了!

FAQ

这是是来自dalao的疑问:

x_o_r:上面那个算法流程第六步在干吗?

aq:这个是算法流程。

x_o_r:为什么要这么做?

aq:这个是流程,你首先假装它是对的。现在你哪里没看懂?

x_o_r:这个算法如何证明复杂度和正确性?

(x_o_r掉线)

XSamsara:为什么流程要先处理轻儿子再处理重儿子?不能反过来吗?

(x_o_r:轻重链剖分不就是暴力吗)

aq:因为这样快啊 这里为了统计这个节点,我们需要开一个桶。呃我知道这个“桶”的概念很抽象,就是理解成为一个颜色的hash数组,它的作用就是记录什么颜色有多少个。举个例子hash[x]=c就是表示x这个颜色有c个。诶如果想看复杂度证明的话,cptraser的博客写了个证明的。

XSamsara:轻重链有什么区别?为什么要区分当前节点是轻儿子还是重儿子?

(片刻)

XSamsara:哦,如果当前节点是轻儿子,最后擦除它是不是为了不影响其他子树的统计?但是为什么还要区分轻重链?是为了正确性还是为了效率?

aq:剖分轻重链的目的就是为了快。比方说我们先从重儿子开始做也可以啊。但是按照轻重儿子的顺序就能够把复杂度控制在$O(nlogn)$。

XSamsara:按照轻重儿子的顺序又会怎么样呢?为什么能够节省复杂度?

aq:考虑我们最最暴力的$O(n^2)$操作,统计完之后为了不影响其他子树统计,我们每一次都需要擦除整颗子树在桶里面的信息。这里,我们先做轻儿子并擦除,最后来统计重儿子,于是我们重儿子的信息就可以先不用擦了,因为当前节点也需要重儿子整颗子树的信息啊!

XSanasara:噢……原来轻重链剖分只是为了效率,我对它的期望太高了,以为是什么玄妙操作……

后续应用

1.dfs序不修改莫队的优秀替代品:$O(\sqrt n) -> O(log n)$

2.然后结合点分治可做一些有根树上的路径统计问题

似乎讲的很清晰易懂啊?

题目

Educational Codeforces Round 2 E Lomsat gelral

You are given a rooted tree with root in vertex 1. Each vertex is coloured in some colour.

Let's call colour c dominating in the subtree of vertex v if there are no other colours that appear in the subtree of vertex v more times than colour c. So it's possible that two or more colours will be dominating in the subtree of some vertex.

The subtree of vertex v is the vertex v and all other vertices that contains vertex v in each path to the root.

For each vertex v find the sum of all dominating colours in the subtree of vertex v.

Input

The first line contains integer n (1 ≤ n ≤ 105) — the number of vertices in the tree.

The second line contains n integers ci (1 ≤ ci ≤ n), ci — the colour of the i-th vertex.

Each of the next n - 1 lines contains two integers xj, yj (1 ≤ xj, yj ≤ n) — the edge of the tree. The first vertex is the root of the tree.

Output

Print n integers — the sums of dominating colours for each vertex.


题目大意

就是询问所有子树中出现次数最多的颜色和。

题目分析

上面应该讲的很清楚了。

 #include<bits/stdc++.h>
typedef long long ll;
const int maxn = ; struct node
{
int fa,tot,son;
ll col;
}a[maxn];
std::vector<int> f[maxn<<];
bool vis[maxn];
int n;
ll cnt[maxn],ans[maxn],mx,sum; void dfs1(int x, int fa)
{
a[x].fa = fa, a[x].son = -, a[x].tot = ;
for (int i=; i<f[x].size(); i++)
if (f[x][i]!=fa){
dfs1(f[x][i], x);
a[x].tot += a[f[x][i]].tot;
if (a[x].son ==-||a[a[x].son].tot < a[f[x][i]].tot)
a[x].son = f[x][i];
}
}
void color(int x, int c)
{
cnt[a[x].col] += c;
if (c>&&mx <= cnt[a[x].col])
{
if (mx < cnt[a[x].col]) mx = cnt[a[x].col], sum = ;
sum += a[x].col;
}
for (int i=; i<f[x].size(); i++)
if (f[x][i]!=a[x].fa&&!vis[f[x][i]])
color(f[x][i], c);
}
void dfs2(int x, bool fl)
{
for (int i=; i<f[x].size(); i++)
if (f[x][i]!=a[x].son&&f[x][i]!=a[x].fa)
dfs2(f[x][i], );
if (a[x].son!=-) dfs2(a[x].son, ), vis[a[x].son] = ;
color(x, );
ans[x] = sum;
if (a[x].son!=-) vis[a[x].son] = ;
if (!fl) color(x, -), mx = sum = ;
}
int main()
{
scanf("%d",&n);
for (int i=; i<=n; i++) scanf("%I64d",&a[i].col);
for (int i=; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
f[x].push_back(y), f[y].push_back(x);
}
dfs1(, );
dfs2(, );
for (int i=; i<=n; i++) printf("%I64d ",ans[i]);
return ;
}

(注意一下要开long long)

END

初涉DSU on tree的更多相关文章

  1. 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,求每个子树中最长的边,满 ...

  2. CF 570D. Tree Requests [dsu on tree]

    传送门 题意: 一棵树,询问某棵子树指定深度的点能否构成回文 当然不用dsu on tree也可以做 dsu on tree的话,维护当前每一个深度每种字母出现次数和字母数,我直接用了二进制.... ...

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

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

  4. CF 375D. Tree and Queries【莫队 | dsu on tree】

    题意: 一棵树,询问一个子树内出现次数$≥k$的颜色有几种 强制在线见上一道 用莫队不知道比分块高到哪里去了,超好写不用调7倍速度!!! 可以用分块维护出现次数这个权值,实现$O(1)-O(\sqrt ...

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

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

  6. UOJ#266. 【清华集训2016】Alice和Bob又在玩游戏 博弈,DSU on Tree,Trie

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ266.html 题解 首先我们可以直接暴力 $O(n^2)$ 用 sg 函数来算答案. 对于一个树就是枚举 ...

  7. dsu on tree入门

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

  8. 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree

    原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...

  9. [Codeforces741D]Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths——dsu on tree

    题目链接: Codeforces741D 题目大意:给出一棵树,根为$1$,每条边有一个$a-v$的小写字母,求每个点子树中的一条最长的简单路径使得这条路径上的边上的字母重排后是一个回文串. 显然如果 ...

随机推荐

  1. C#求圆的周长、面积、体积

    窗体应用程序 private void button1_Click(object sender, EventArgs e) { double r; r = Convert.ToInt32(textBo ...

  2. 爬虫—使用Requests

    一,安装 pip install requests 二,基本用法 1.简单示例 import requests res = requests.get('https://www.baidu.com') ...

  3. dbutils下载

  4. 解读ping -n 4 127.1 >nul 2>nul

    命令解读 ping是Windows.Unix和Linux系统下的一个命令.ping也属于一个通信协议,是TCP/IP协议的一部分.利用"ping"命令可以检查网络是否连通,可以很好 ...

  5. Technocup 2017 - Elimination Round 1 (Unofficially Open for Everyone, Rated for Div. 2) C

    This is an interactive problem. You should use flush operation after each printed line. For example, ...

  6. php pack、unpack、ord 函数使用方法(二进制流接口应用实例)

    在工作中,我也逐渐了解到pack,unpack,ord对于二进制字节处理的强大. 下面我逐一介绍它们.在我们工作中,用到它们的估计不多. 我在最近一个工作中,因为通讯需要用到二进制流,然后接口用php ...

  7. Java GUI setSize()、setPreferredSize()的区别

    setSize().setPreferredSize()都可以设置组件的大小,但二者的使用有所不同. 1.setSize()的使用方式 setSize(int width,int height) se ...

  8. 自定义orgmode中加粗字体的颜色

    自定义orgmode中加粗字体的颜色 Table of Contents 1. orgmode中加粗字体的默认处理 2. 设置设置加粗字体的颜色 1 orgmode中加粗字体的默认处理 在orgmod ...

  9. nodejs中的异步回调机制

    1.再次clear Timer定时器的作用 setTimeOut绝非是传统意义上的“sleep”功能,它做不到让主线程“熄火”指定时间,它是用来指定:某个回调在固定时间后插入执行栈!(实际执行时间略长 ...

  10. DVWA之命令注入(command injection)

    Command injection就是指通过提交恶意构造的参数破坏命令语句结构,从而达到执行恶意命令的目的 LOW 无论是Windows还是Linux,都可以使用&&连接多个命令 执行 ...