树上启发式合并

本文介绍了树上启发式合并的思想,它适用于离线查询且不带修改的子树查询问题。通过将问题转化为子树查询,利用重儿子概念减少重复计算,达到O(nlogn)的时间复杂度。文章详细分析了算法流程,包括轻重链剖分、启发式合并的实现以及时间复杂度,并给出例题和代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


又称dsu on tree,但这个算法和并查集没啥联系,可能借鉴了按秩合并的思想吧。

速通版:
问题如果可以转化成关于子树的查询,(比如查询某棵子树内的信息、比如查询兄弟子树的信息),那么可以先尝试暴力做法:
对于rt,我们一棵一棵地遍历它的子树,每棵子树都是先统计答案,然后更新相关信息。 如果我们枚举每棵子树,统计答案,然后清空信息,这样是标准的O(N²), 但是我们发现,最多可以保留1棵子树的信息不清空因为回到rt时这颗子树和前面0棵子树的信息不会产生答案贡献,从第二颗子树开始才会产生贡献, 所以我们希望让保留信息的子树尽量大,于是结合重儿子的概念,保留重子树,这样可以证明是O(Nlogn)

也就是说,dsu on tree仅仅是改了个dfs的顺序,让重子树最后计算,并保留它的信息给根节点。

算法原理+流程+时间复杂度分析

参考大佬博客传送

我们先从第一个例题开始看起:

E. Lomsat gelral
题意是给定了一个无根树,带点权,q次询问,每次询问某棵子树内,点权众数之和。(如果2 3 4都是众数,那么答案是2+3+4=9)

首先最直接的暴力方法就是,枚举每棵子树,通过遍历其节点O(n)求出答案,并删除记录的信息,时间复杂度是O(n²)

优化方法,我们注意到对于根节点rt,遍历完其所有孩子节点的子树后,最后递归回来的这颗子树不需要删除,可以直接由rt继承,我们自然想到最后这个子树的size越大
越好,这便是所谓启发式合并。 子树size最大不正是轻重链剖分里面的重儿子的概念吗? 所以算法流程就出来了:

  • 首先轻重链剖分,求出重儿子

  • 全局维护出现众数的出现次数,以及所求的答案,进行dfs。优先dfs轻儿子,最后dfs重儿子,重儿子的影响保留,rt重新统计轻儿子的贡献并记录答案,如果rt是其父节点的轻儿子,删除rt的影响,否则保留。(建议看代码以及注释)

  • 删除操作就是暴力递归给轻儿子,每到一个节点就消除影响。

  • 时间复杂度分析 ,我们考虑某个节点u,u被访问只有两种情形:
    ①通过重链被访问,这种情况只会访问1次 ,因为重链的影响是永久性的,不会被删
    ②通过轻边被访问,由于树剖后根节点到u只有logn条轻边,所以u只会被访问logn次。
    综上时间复杂度为nlogn
    例题代码:

#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
#define pdd pair<double, double>
#define re register
#define lc rt<<1
#define rc rt<<1|1
const int maxn = 1e5 + 10;
const ll mod = 998244353;
const ll inf = (ll)4e17+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
//给定树 带点权 询问子树中,出现次数最多的若干种点权的点权和 (众数之和)
//暴力解法 对于每个子树都花O(n)求出其答案,并用O(N)清空影响  时间O(N平方)
//发现对于根节点rt,最后递归的子树可以不删,保留信息给根节点,我们最后递归重儿子,保留重儿子的信息 即:启发式合并
//我们保留重儿子,删除轻儿子的影响,并最后重新统计一遍轻儿子
//nlogn 某个点u被访问:要么通过重链(不会被删,只会被访问1次),要么通过轻边,但根到u只有logn条轻边,所以最多logn次
vector<int> g[maxn];
int v[maxn];
int cnt[maxn];
int mmx=-1;//众数出现次数
ll ret, ans[maxn];
int n;
//轻重链剖分
int son[maxn],siz[maxn];
void dfs(int rt,int fa) 
{
   
	siz[rt]=1;
	for(int &i:g[rt]) 
	{
   
		if
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值