首页 > 其他分享 >树上启发式合并学习笔记

树上启发式合并学习笔记

时间:2023-04-30 22:12:54浏览次数:52  
标签:fa int wson 笔记 100005 ++ 启发式 树上 size

最近几天了解到一个很神奇的算法——dsu on tree,看上去没多快实际上很快,这叫低调。

好久不更了,至于反演,5 月再更吧,4 月的最后一天分享一下 dsu on tree。顺便闲话一句,4/26 是我生日,也是历史二模。

重链剖分 dsu on tree

这类 dsu on tree 适用于多次询问,每次询问需要 $O(子树大小)$ 的时间,这时用重链剖分 dsu on tree 可以优化到 $O(q\log n)$

例题1:

CF600E,典中典了。

给一棵以 $1$ 为根的树,每个点都有一个颜色,求出以每个点为根的子树中主导颜色的编号之和。

所谓主导颜色,就是出现次数最多的颜色,可能有多个。

考虑一个朴素的做法:

开一个桶存储每个颜色的出现次数,按照 DFS 序遍历,遍历到一个点时,依次统计子结点的答案,统计完一个就把桶清空。

接着再把它子结点的颜色加到桶里,顺便处理出以当前节点为子树的主导颜色。

这样的做法为 $O(n\times n)$,超时。

 

但是可以优化,我们定义一个非叶子节点的重儿子是 $size$ 最大的儿子,轻儿子是除了重儿子外的儿子。

对于一个节点 $x$,最后一个儿子算完之后是不用清空桶的,它可以直接记录到以 $x$ 为根的子树的答案中,剩下的依旧是暴力。

显然把最后一个儿子设为重儿子最优了,优化成了 $O(n\log n)$。

 

下面来证明一下,每个节点被统计的次数为它上面的轻边数量(如果是重边,统计完就会给它的父亲了)。

每经过一个轻边,它的兄弟中就一定有一个 size 比它大的。

所以经过后子树的 $size$ 至少翻倍,$\log n$ 次 到达根,每个节点被统计 $\log n$ 次,$n$ 个就被统计 $n\log n$ 次了,证毕。

 

注意:清空桶时,如果全清空还是超时,所以只需要清空用过的就行了。

这里使用 $map$,虽然时间复杂度会多一个 $\log$,但是一句 m.clear() 就行了还很快。

 

代码($n\log^2 n$ 的):

#include <map>
#include <vector>
#include <iostream>
#define int long long
using namespace std;
int n, ma, res;
int fa[100005], sz[100005], wson[100005];
int c[100005], ans[100005];
vector <int> v[100005];
map <int, int> m, b;
int dfs1 (int x) {
    for (int i = 0; i < v[x].size (); i ++) if (v[x][i] != fa[x]) {
        fa[v[x][i] ] = x;
        int tmp = dfs1 (v[x][i]);
        sz[x] += tmp;
        if (sz[wson[x] ] < tmp) wson[x] = v[x][i];
    }
    return ++ sz[x];
}
void add (int x) {
    m[x] ++;
    if (m[x] > ma) {
        ma = m[x];
        res = x;
    } else if (m[x] == ma) res += x;
}
void dfs2 (int x) {
    add (c[x]);
    for (int i = 0; i < v[x].size (); i ++) if (v[x][i] != fa[x]) dfs2 (v[x][i]);
}
void dfs_ans (int x) {
    for (int i = 0; i < v[x].size (); i ++) {
        if (v[x][i] != fa[x] && v[x][i] != wson[x]) {
            dfs_ans (v[x][i]);
            m.clear ();
            res = ma = 0;
        }
    } 
    if (wson[x] != 0) dfs_ans (wson[x]);
    add (c[x]);
    for (int i = 0; i < v[x].size (); i ++) if (v[x][i] != fa[x] && v[x][i] != wson[x]) dfs2 (v[x][i]);
    ans[x] = res;
}
signed main () {
    int x, y;
    scanf ("%lld", &n);
    for (int i = 1; i <= n; i ++) cin >> c[i];
    for (int i = 1; i < n; i ++) {
        scanf ("%lld%lld", &x, &y);
        v[x].push_back (y);
        v[y].push_back (x);
    }
    dfs1 (1);
    dfs_ans (1);
    for (int i = 1; i <= n; i ++) printf ("%lld ", ans[i]);
    return 0;
}

 例题2:

CF375D

关于询问,我们用一个 vector<pair<int, int> > v[100005] 来记录,v[i] 存的是询问中的第一个参数为 i 的。

pair 的第一个是 $k$,第二个是询问编号,每遍历到一个点,处理完信息后,访问 v[x] 并把询问搞掉。

桶维护的是颜色出现的次数,$BIT$ 维护的是颜色出现次数的出现次数。

对于这题,可以用一个桶和一个 $BIT$ 来维护。

先来考虑询问,假设当前的桶和 $BIT$ 已经维护好了,要求出现次数 $\geq k$ 的颜色数量,query (100000) - query (k - 1) 即可。

每次把 $BIT$ 和桶清空,还是最后一个遍历重儿子。

这里为了方便,两者都用 $map$ 写,所以时间复杂度是 dsu on tree 的一个 $\log$,$BIT$ 的一个 $log$,$map$ 的一个 $\log$。

$n\log^3 n$,但是凭借 $BIT$ 和 dsu on tree的超小常数,过了。

代码:

#include <map>
#include <vector>
#include <iostream>
using namespace std;
int n, q;
int fa[100005], sz[100005], wson[100005];
int c[100005], ans[100005];
vector <pair <int, int> > v[100005];
vector <int> G[100005];
map <int, int> sum, b;//sum: 树状数组,维护每个数出现次数的出现次数
int dfs1 (int x) {//剖链
    for (int i = 0; i < G[x].size (); i ++) if (G[x][i] != fa[x]) {
        fa[G[x][i] ] = x;
        int tmp = dfs1 (G[x][i]);
        sz[x] += tmp;
        if (sz[wson[x] ] < tmp) wson[x] = G[x][i];
    }
    return ++ sz[x];
}
void add (int x, int y) {for (; x <= 100000; x += x & -x) sum[x] += y;}
int query (int x) {
    int ret = 0;
    for (; x > 0; x -= x & -x) ret += sum[x];
    return ret;
}
void dfs2 (int x) {//重新处理,之前的被清空了。 
    if (b[c[x] ] != 0) add (b[c[x] ], -1);
    ++ b[c[x] ];
    add (b[c[x] ], 1);
    for (int i = 0; i < G[x].size (); i ++) if (G[x][i] != fa[x]) dfs2 (G[x][i]);
}
void dfs_ans (int x) {//统计以 x 为根的子树的答案。 
    for (int i = 0; i < G[x].size (); i ++) if (G[x][i] != fa[x] && G[x][i] != wson[x]) {
        dfs_ans (G[x][i]);
        sum.clear ();
        b.clear ();
    }
    if (wson[x] != 0) dfs_ans (wson[x]);
    if (b[c[x] ] != 0) add (b[c[x] ], -1);
    ++ b[c[x] ];
    add (b[c[x] ], 1);
    for (int i = 0; i < G[x].size (); i ++) if (G[x][i] != fa[x] && G[x][i] != wson[x]) dfs2 (G[x][i]);
    for (int i = 0; i < v[x].size (); i ++)
        ans[v[x][i].second] = query (100000) - query (v[x][i].first - 1);
    //second 是询问编号,first 是 k。出现次数在 1 ~ 100000 的减去在 1 ~ k - 1 的得到在 1 ~ k 的。 
}
int main () {
    int x, y;
    scanf ("%d%d", &n, &q);
    for (int i = 1; i <= n; i ++) scanf ("%d", &c[i]);
    for (int i = 1; i < n; i ++) {
        scanf ("%d%d", &x, &y);
        G[x].push_back (y);
        G[y].push_back (x);
    }
    dfs1 (1);
    for (int i = 1; i <= q; i ++) {
        scanf ("%d%d", &x, &y);
        v[x].push_back (make_pair (y, i) );
    }
    dfs_ans (1);
    for (int i = 1; i <= q; i ++) printf ("%d\n", ans[i]);
    return 0;
}

 长链剖分 dsu on tree

重链剖分 dsu on tree 的题目还有很多,这里不再详解了。长链剖分才是优美中的优美,以 $O(n)$ 解决问题。

它适用于解决这类问题:多次询问,每次询问也是求以 $x$ 为根的子树内的东西,而直接暴力的复杂度是 $depth[x]$ 的。(即以 $x$ 为根的子树的深度)

例题:

CF1009F

大家可以自己先去想一想。

标签:fa,int,wson,笔记,100005,++,启发式,树上,size
From: https://www.cnblogs.com/Xy-top/p/17365824.html

相关文章

  • 数学学习笔记
    学习了基础的数学,发现我的数学还(fei)算(chang)可(la)以(ji),不多说了,开启美妙的数xiao学之旅吧。进制转换首先是我们熟悉的进制转换,就是n进制转m进制。要把n进制数转化十进制数,再把十进制数转化为m进制数。把n进制数转换为十进制数要先模再除,具体过程就不赘述了,把十进制数转换为m进制数......
  • 构建之法阅读笔记2
    《构建之法》这本书有哪些优点?又有哪些不足之处?优点:1、语言生动有趣,采用情景式、对白式的方式对在软件工程相关的学习中重现场景,更好的解决了读者所遇到相类似的问题。   2、注重实践。在大部分时候,大学的计算机专业,理论和实践是分离的,甚至只注重理论,讲一堆概念,定义,然而......
  • 构建之法读书笔记03
    第二章个人技术和流程2.1单元测试①重要的单元测试:有效解决程序员对模块功能的误解、疏忽或不了解模块的变化之类的问题,使自己负责的模块功能定义尽量明确,模块的质量得到稳定的、量化的保证。②好的单元测试的标准:在最基本的功能/参数上验证程序的正确性单元测试必须由最熟......
  • 构建之法读书笔记-4月-2
    《构建之法》一书共分四部分,详细介绍了具有创新性、高度可靠性的软件架构设计的方法及工具,这里主要介绍第三部分和第四部分的内容。第三部分介绍了如何针对不完美的现实环境进行系统设计,并以适应环境变化和不确定性为目标,最大限度地减少风险并提升可靠性。本部分重点关注于“鲁......
  • 构建之法阅读笔记03
    软件架构是什么?软件架构是指对软件系统的整体结构和组织方式的定义。它包括系统的各个组成部分、它们之间的关系、以及系统的行为和性能等方面。软件架构的重要性软件架构是软件开发的基础,它决定了系统的可维护性、可扩展性、可靠性和安全性等方面。良好的软件架构能够降低系统维......
  • 堆与二叉搜索树学习笔记
    部分内容来自OI-WIKI。1.堆堆的定义堆是一棵二叉树,满足每个节点的键值都大于等于它的父亲节点或者小于等于它的父亲节点。每个节点的键值都大于等于它的父亲节点的叫小根堆,每个节点的键值都小于等于它的父亲节点的叫大根堆。优先队列是一种抽象数据类型,它是一种容器,里面有......
  • 嵌入式学习笔记汇总
    本文整理STM32、STM8和uCOS-III的所有文章链接。STM32学习笔记目录源码:mySTM32-learnSTM32学习笔记(1)——LED和蜂鸣器STM32学习笔记(2)——按键输入实验STM32学习笔记(3)——时钟系统STM32学习笔记(4)——NVIC中断优先级管理和外部中断EXTISTM32学习笔记(5)——系统定时器SysTickS......
  • Vulnhub靶机笔记2——matrix-breakout-2-morpheus
    一、介绍一个以《黑客帝国》为背景的靶场涉及内容主机发现端口服务扫描1.2不用工具实现ffuf目录爆破一句话木马反弹shellmsf,蚁剑使用图片隐写CVE-2022-0847漏洞利用二、环境攻击机:kali靶机:matrix-breakout-2-morpheus三、过程1、信息收集1.1主机存活扫描nma......
  • 基于C#的excel笔记
    一、引用的excel库1、Microsoft.Office.Interop.Excel库效果不好,代码繁琐。在执行语句时出现不能解决的BUG,usingExcel=Microsoft.Office.Interop.Excel;...Excel.Workbookworkbook=excelApp.Workbooks.Add();//X//要生成的字符串////stringinputStri......
  • 外设驱动库开发笔记53:MAX31856热偶变送器驱动
      在我们的产品中经常有需要温度检测的地方,而热电偶温度检测电路是我们常用的。热电偶温度检测的方法很多,有时出于简单方便的考虑我们会选择热偶温度变送器来实现,这一篇我们就来讨论使用MAX31856热电偶温度变送器实现温度的检测。1、功能概述  MAX31856可以对任何类型热电偶......