dsu on tree 入门
Dus on tree
树上并查集?。 啊这,并不是的啦,他利用了树上启发式合并的思想。
他主要解决不带修改且主要询问子树信息的树上问题。
先来看到例题,CF600E 。
这不就是树上莫队的经典题吗?。 会莫队的大佬一眼就秒了。
不会的蒟蒻我只能打打暴力,骗骗分。
首先,我们暴力其实很好打,就是对每个点都统计一下他子树的答案,时间复杂度为 O(\(n^2\)).
这显然,我们是不能接受的,我们需要优化。
Dus on tree 利用轻重链剖分的思想,把他的复杂度优化为 O(\(n log n\)) 的。
我们递归处理子树的时候,重复计算了很多种状态,我们就要考虑剪枝,减去重复的状态。
实际上,我们最后处理的子树肯定是不需要删除他的贡献的,在计算他的父亲 \(x\) 的答案的时候,直接利用他的信息,
就可以直接推出他父亲的答案。
我们就要确定一种顺序,使我们遍历子树节点的数目尽可能少。
我们选的最后遍历的要为他的重儿子(重儿子子树中的节点是最多的)
就这样 Dus on tree 的算法流程就是:
递归处理每个轻儿子,同时消除轻儿子的影响。
递归重儿子,不消除重儿子的影响。
统计轻儿子对答案的贡献。
将轻儿子和重儿子的信息合并,得出这个节点的答案。
消除轻儿子对答案的影响
大致的代码张这样:
void dfs(int x,int fa,int opt)
{
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa || to == son[x]) continue;
dfs(to,1);//递归轻儿子
}
if(son[x]) dfs(son[x],0);//帝国重儿子
add(x);//统计轻儿子对答案的贡献
ans[x] = now_ans;//合并轻儿子和重儿子得出这个点的答案
if(opt == 1) delet(x);//如果他是轻儿子,消除他的影响
}
图例长这样
紫色的是他的轻儿子,红色的是他的重儿子,序号是他的遍历顺序。
这不是和普通的爆搜没什么区别吗?为什么复杂度不是O(\(n^2\))
下面,我们简单证明一下他的复杂度,不愿意看的可以直接跳过。
性质:一个节点到根的路径上轻边个数不会超过 logn 条
证明:设根到该节点有 \(x\) 的轻边,该节点的大小为 \(y\),根据轻重边的定义,轻边所连向的点的大小不会成为该节点
总大小的一般。这样每经过一条轻边,\(y\) 的上限就会 /2,所以 \(x\) < \(log n\)
然而这条性质并不能解决问题,我们考虑一个点会被访问多少次
一个点被访问到,只有两种情况
1、在暴力统计轻边的时候访问到。根据前面的性质,该次数 < \(log n\)
2、通过重边 在遍历的时候被访问到,显然只有一次
如果统计一个点的贡献的复杂度为O(1)的话,该算法的复杂度为O(\(nlog n\))
我们上面的例题就可以直接套板子了。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int N = 1e5+10;
int n,m,tot,u,v,max_c,now_ans,heavy_son;
int siz[N],fa[N],son[N],head[N],cnt[N],c[N],ans[N];
struct node
{
int to,net;
}e[N<<1];
void add_(int x,int y)
{
e[++tot].to = y;
e[tot].net = head[x];
head[x] = tot;
}
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
return s * w;
}
void get_tree(int x)
{
siz[x] = 1;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa[x]) continue;
fa[to] = x;
get_tree(to);
siz[x] += siz[to];
if(siz[son[x]] < siz[to]) son[x] = to;
}
}
void add(int x,int val)
{
cnt[c[x]] += val;
// printf("----------->\n");
// cout<<max_c<<" "<<now_ans<<endl;
if(cnt[c[x]] > max_c)//max_c 记录当前出现次数最多的颜色的出现次数
{
max_c = cnt[c[x]];
now_ans = c[x];
}
else if(cnt[c[x]] == max_c)//相等的话,记录一下编号和
{
now_ans += c[x];
}
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa[x] || to == heavy_son) continue;
add(to,val);
}
}
void dfs(int x,int type)
{
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa[x] || to == son[x]) continue;
dfs(to,1);
}
if(son[x])
{
dfs(son[x],0);
heavy_son = son[x];
}
add(x,1);
ans[x] = now_ans;
heavy_son = 0;
if(type == 1)
{
add(x,-1);
max_c = now_ans = 0;
}
}
signed main()
{
n = read();
for(int i = 1; i <= n; i++) c[i] = read();
for(int i = 1; i <= n-1; i++)
{
u = read(); v = read();
add_(u,v); add_(v,u);
}
get_tree(1); dfs(1,0);
for(int i = 1; i <= n; i++) printf("%lld ",ans[i]);
return 0;
}
一个需要注意的点是 不能记录出现次数最多的颜色编号,而应该记录这个颜色出现的次数。(我在这里卡了好几回)
因为你这样会重复计算,第二组样例就是一组 很好的 Hack 数据。
dsu on tree 入门的更多相关文章
- dsu on tree入门
先瞎扯几句 说起来我跟这个算法好像还有很深的渊源呢qwq.当时在学业水平考试的考场上,题目都做完了不会做,于是开始xjb出题.突然我想到这么一个题 看起来好像很可做的样子,然而直到考试完我都只想出来一 ...
- 【CodeForces】600 E. Lomsat gelral (dsu on tree)
[题目]E. Lomsat gelral [题意]给定n个点的树,1为根,每个点有一种颜色ci,一种颜色占领一棵子树当且仅当子树内没有颜色的出现次数超过它,求n个答案——每棵子树的占领颜色的编号和Σc ...
- [探究] dsu on tree,一类树上离线问题的做法
dsu on tree. \(\rm 0x01\) 前言\(\&\)技术分析 \(\bold{dsu~on~tree}\),中文别称"树上启发式合并"(虽然我并不承认这种称 ...
- dsu on tree(树上启发式合并)
简介 对于一颗静态树,O(nlogn)时间内处理子树的统计问题.是一种优雅的暴力. 算法思想 很显然,朴素做法下,对于每颗子树对其进行统计的时间复杂度是平方级别的.考虑对树进行一个重链剖分.虽然都基于 ...
- 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,求每个子树中最长的边,满 ...
- CF 570D. Tree Requests [dsu on tree]
传送门 题意: 一棵树,询问某棵子树指定深度的点能否构成回文 当然不用dsu on tree也可以做 dsu on tree的话,维护当前每一个深度每种字母出现次数和字母数,我直接用了二进制.... ...
- [dsu on tree]【学习笔记】
十几天前看到zyf2000发过关于这个的题目的Blog, 今天终于去学习了一下 Codeforces原文链接 dsu on tree 简介 我也不清楚dsu是什么的英文缩写... 就像是树上的启发式合 ...
- CF 375D. Tree and Queries【莫队 | dsu on tree】
题意: 一棵树,询问一个子树内出现次数$≥k$的颜色有几种 强制在线见上一道 用莫队不知道比分块高到哪里去了,超好写不用调7倍速度!!! 可以用分块维护出现次数这个权值,实现$O(1)-O(\sqrt ...
- dsu on tree 树上启发式合并 学习笔记
近几天跟着dreagonm大佬学习了\(dsu\ on\ tree\),来总结一下: \(dsu\ on\ tree\),也就是树上启发式合并,是用来处理一类离线的树上询问问题(比如子树内的颜色种数) ...
随机推荐
- 【转】对初学LoadRunner朋友的建议
对初学LoadRunner朋友的建议 作者:wind摘要:随着Internet的普及与迅速发展,企业业务量的迅速加大,数据大集中成为一种趋势,IT系统承载的负荷越来越重,系统性能的好坏严重的影响了企业 ...
- 据说是最好的记忆工具——Anki
http://www.ankichina.net/ .u1s1,确实挺好用,自建题库,全程自助. 可以插入文字.图片.音频,会安排合理的复习频率,可以随时同步,电脑手机版本全.
- RPC之总体架构
要完成一个高可用.高性能的RPC框架,需要对其架构的设计进行梳理,这里参考xxl-rpc框架,对整个项目进行梳理. 以上就是项目的整个构架,分为四个部分: 第一个是服务发布与引入,基于JDK动态代理以 ...
- [BUUOJ记录] [BJDCTF2020]EasySearch
前面的突破点考察swp泄露以及md5截断认证,最后一步考察ssi注入 进入题目是一个登陆页面什么提示都没有,工具扫了一下发现swp泄露,得到登录验证页面的源码: <?php ob_start() ...
- python-opencv 图像捕捉多个不规则轮廓,与轮廓内接区域(圆/矩形)思路-持续更新编辑中(会附上详细的思路解释和图片)
整体思路: 1.原图灰度化 2.灰度图截取mask区域 3.mask区域二值化 4.二值化图像运算(开运算) 5.原灰图轮廓提取 6.不规则轮廓校准(外接矩形/内接矩形) 注:代码依次头尾连接哦! 0 ...
- PIoU Loss:倾斜目标检测专用损失函数,公开超难倾斜目标数据集Retail50K | ECCV 2020 Spotlight
论文提出从IoU指标延伸来的PIoU损失函数,能够有效地提高倾斜目标检测场景下的旋转角度预测和IoU效果,对anchor-based方法和anchor-free方法均适用.另外论文提供了Retail5 ...
- opentracting+jager分布式链路追踪探索实践
一.Opentracing opentracing通过提供平台无关.厂商无关的API,使得开发人员可以方便地实现追踪系统.opentracing提供了用于运营支撑系统和针对特定平台的辅助程序库,被跟踪 ...
- Java字符串的常用方法
[转换] //int 10进制----> 转16进制Integer.toHexString(10) // int 10进制----> 转8进制Integer.toOctalString(1 ...
- 10.深入k8s:调度的优先级及抢占机制源码分析
转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 源码版本是1.19 上一篇我们将了获取node成功的情况,如果是一个优先pod获取nod ...
- [剑指Offer]26-树的子结构
题意 判断一棵树(参数二)是不是另一棵树(参数一)的子结构. 题解 递归第一棵树,找两棵树中值一样的节点.若找到后,用另一个函数判断以相同值得节点为根的树2是不是树1的子结构. 代码 class Tr ...