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 入门的更多相关文章

  1. dsu on tree入门

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

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

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

  3. [探究] dsu on tree,一类树上离线问题的做法

    dsu on tree. \(\rm 0x01\) 前言\(\&\)技术分析 \(\bold{dsu~on~tree}\),中文别称"树上启发式合并"(虽然我并不承认这种称 ...

  4. dsu on tree(树上启发式合并)

    简介 对于一颗静态树,O(nlogn)时间内处理子树的统计问题.是一种优雅的暴力. 算法思想 很显然,朴素做法下,对于每颗子树对其进行统计的时间复杂度是平方级别的.考虑对树进行一个重链剖分.虽然都基于 ...

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

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

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

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

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

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

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

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

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

随机推荐

  1. JS开发必须知道的41个技巧

    JS是前端的核心,但有些使用技巧你还不一定知道:本文梳理了JS的41个技巧,帮助大家提高JS的使用技巧: Array 1.数组交集 普通数组 const arr1 = [, , , , , ,],ar ...

  2. Asp.Net Core3.x中使用Cookie

    在Asp.Net中使用Cookie相对容易使用,Request和Response对象都提供了Cookies集合,要记住是从Response中存储,从Request中读取相应的cookie.Asp.Ne ...

  3. 关于java的二维码的生成与解析

    本文说的是通过zxing实现二维码的生成与解析,看着很简单,直接上代码 import java.io.File; import java.io.IOException; import java.nio ...

  4. Spring Cloud:Consul基础知识

    一.基本概念 Consul是一套开源的分布式服务发现和配置管理系统,由HashiCorp公司用Go开发. 它提供微服务系统中的服务治理.配置中心.控制总线等功能. 服务发现:提供HTTP和DNS两种发 ...

  5. 伪距定位算法(matlab版)

    在各种伪距定位算法中,最小二乘法是一种比较简单而广泛的方法,该算法可以分为以下几步: 1.准备数据与设置初始值 这里准备数据,主要是对于各颗可见卫星,收集到它们在同一时刻的伪距测量值,计算测量值的各项 ...

  6. python-opencv 图像捕捉多个不规则轮廓,与轮廓内接区域(圆/矩形)思路-持续更新编辑中(会附上详细的思路解释和图片)

    整体思路: 1.原图灰度化 2.灰度图截取mask区域 3.mask区域二值化 4.二值化图像运算(开运算) 5.原灰图轮廓提取 6.不规则轮廓校准(外接矩形/内接矩形) 注:代码依次头尾连接哦! 0 ...

  7. redis锁操作

    模拟多线程触发 package com.ws.controller; import io.swagger.annotations.Api; import io.swagger.annotations. ...

  8. Spring Boot入门,源码解析

    目录 1.Spring Boot简介 2.微服务 3.Spring Boot HelloWorld 3.1 创建一个Maven工程 3.2 导入依赖Spring Boot相关的依赖 3.3 编写一个主 ...

  9. SpringBoot整合MinIO

    今天因为公司的需求接触到这个东西,我们先来看下MinIO的官网简介 MinIO 是一个基于Apache License v2.0开源协议的对象存储服务.它兼容亚马逊S3云存储服务接口,非常适合于存储大 ...

  10. Unit4:碎片

    基本使用 动态调用 碎片和活动 碎片周期