首页 > 其他分享 >图论专题-学习笔记:树上启发式合并(dsu on tree)

图论专题-学习笔记:树上启发式合并(dsu on tree)

时间:2022-10-05 10:55:47浏览次数:87  
标签:贡献 遍历 int dsu tree 儿子 MAXN 启发式 now

目录

1. 前言

树上启发式合并(dsu on tree),是一种类似于启发式合并的方式解决关于部分子树内问题的算法,一般都是什么子树内颜色个数等等的。

前置知识:求重儿子(就是树剖那个重儿子)。

2. 详解

树上启发式合并的一般步骤如下:

  1. 求出重儿子。
  2. 遍历整棵树,对于一个点 \(x\) 按照如下方式求出其答案:
    1.1 首先直接遍历其所有轻儿子求出其所有轻儿子的答案,但是不保留轻儿子对父亲的贡献。
    1.2 遍历重儿子,求出重儿子答案,并保留重儿子对父亲的贡献。
    1.3 再次通过 dfs 序(或别的方法,但不要直接遍历)遍历轻儿子,求出轻儿子对父亲的贡献。
    1.4 如果当前节点是其父亲的轻儿子,删除子树所有节点对答案的影响。

这么说不是特别好懂,上例题:CF600E Lomsat gelral

首先默认都会求重儿子,下记 \(cnt_i\) 表示颜色 \(i\) 的出现次数,\(sum\) 表示答案,\(Maxn\) 表示 \(cnt_i\) 最大值。

考虑 1.1 步遍历轻儿子,对于每个轻儿子算一次答案,那么所有轻儿子都会在 \(cnt_i\) 上有贡献,但是因为是轻儿子所以 1.4 步中要删除。

考虑 1.2 步遍历重儿子,但是因为是重儿子所有 1.4 步不会删除重儿子对当前节点的贡献。

考虑 1.3 步遍历轻儿子,这里使用 dfs 序遍历,因为一个点子树内 dfs 序是连续的(实现看代码),重新计算轻儿子的贡献,颜色加入到 \(cnt_i\) 中。

通过 \(sum,Maxn\) 获取答案,然后如果是轻儿子那么有 1.4 步删除影响,体现到这题中就是 \(sum,Maxn\) 重置为 0。

发现上述过程中我们只遍历了两次轻儿子一次重儿子,是非常不错的选择。

复杂度是 \(O(n\log n)\),但是我不会证()

关于 \(sum,Maxn\) 删除时重置为 0,原因是算答案是一定是先算轻儿子再算重儿子最后重新算轻儿子,因此如果当前点是轻儿子,1.4 步直接重置一定是对的,因为前面没有任何节点在 \(cnt_i\) 上有贡献。

至于为什么 1.3 步说不要直接遍历轻儿子,主要考虑重新遍历后此时轻儿子的贡献就要被保留,然后你还要重新计算一遍轻儿子的答案,此时轻儿子会被遍历多次,并且是否保留贡献会混乱,没准什么时候不该算的贡献就算进去了,时间和正确性都没有保证。

Code:

/*
========= Plozia =========
	Author:Plozia
	Problem:CF600E Lomsat gelral
	Date:2022/10/5
========= Plozia =========
*/

#include <bits/stdc++.h>
typedef long long LL;

const int MAXN = 1e5 + 5;
int n, a[MAXN], Head[MAXN], cntEdge, Size[MAXN], l[MAXN], Son[MAXN], ys[MAXN], fa[MAXN], cnt[MAXN], Maxn, r[MAXN];
LL ans[MAXN], sum;
struct node { int To, Next; } Edge[MAXN << 1];

int Read()
{
	int sum = 0, fh = 1; char ch = getchar();
	for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
	return sum * fh;
}
void add(int x, int y) { ++cntEdge; Edge[cntEdge] = (node){y, Head[x]}; Head[x] = cntEdge; }
void Add(int p) { ++cnt[a[p]]; if (cnt[a[p]] == Maxn) sum += a[p]; else if (Maxn < cnt[a[p]]) { Maxn = cnt[a[p]]; sum = a[p]; } }
void Del(int p) { --cnt[a[p]]; }

void dfs1(int now, int f)
{
	Size[now] = 1, l[now] = ++l[0]; ys[l[0]] = now; fa[now] = f;
	for (int i = Head[now]; i; i = Edge[i].Next)
	{
		int u = Edge[i].To; if (u == f) continue ;
		dfs1(u, now); Size[now] += Size[u]; if (Size[u] > Size[Son[now]]) Son[now] = u;
	}
	r[now] = l[0]; // 通过这一步操作可以直接利用 dfs 序遍历子树
}
void dfs2(int now, int f, int opt) // opt 表示 now 是不是 f 的重儿子
{
	for (int i = Head[now]; i; i = Edge[i].Next)
	{
		int u = Edge[i].To; if (u == f || u == Son[now]) continue ;
		dfs2(u, now, 0);
	} // 计算轻儿子答案
	if (Son[now]) dfs2(Son[now], now, 1); // 计算重儿子答案和贡献
	for (int i = Head[now]; i; i = Edge[i].Next)
	{
		int u = Edge[i].To; if (u == f || u == Son[now]) continue ;
		for (int j = l[u]; j <= r[u]; ++j) Add(ys[j]);
	} // 重新计算轻儿子贡献
	Add(now); ans[now] = sum;
	if (opt == 0)
	{
		for (int i = l[now]; i <= r[now]; ++i) Del(ys[i]);
		sum = Maxn = 0;
	} // 如果是轻儿子删除贡献
}

int main()
{
	n = Read(); for (int i = 1; i <= n; ++i) a[i] = Read();
	for (int i = 1; i < n; ++i) { int x = Read(), y = Read(); add(x, y); add(y, x); }
	dfs1(1, 1); dfs2(1, 1, 0);
	for (int i = 1; i <= n; ++i) printf("%lld%c", ans[i], " \n"[i == n]);
	return 0;
}

3. 总结

树上启发式合并,考虑先遍历轻儿子不保留贡献,再遍历重儿子保留贡献,再通过 dfs 序等遍历轻儿子保留贡献,最后若是轻儿子就删除贡献即可。

实际上,树上启发式合并对于子树内统计类问题还是有不错的发挥的,而且比隔壁线段树合并好写多了。

练习:CF741D,CF1709E。

标签:贡献,遍历,int,dsu,tree,儿子,MAXN,启发式,now
From: https://www.cnblogs.com/Plozia/p/16755204.html

相关文章

  • 不相交数据结构中的启发式策略
    目录启发式策略(heuristic)元启发式策略(metaheuristic)启发式策略(heuristic)是一类在求解某个具体问题时,在可以接受的时间和空间内能给出其可行解,但又不保证求得最优解(以及可......
  • leetcode 235. Lowest Common Ancestor of a Binary Search Tree 二叉搜索树的最近公
    一、题目大意给定一个二叉搜索树,找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树T的两个结点p、q,最近公共祖先表示为一个结点......
  • 代码随想录训练营|Day 14|Binary Tree, 144, 145, 94
    BinaryTrees树是由结点或顶点和边组成的(可能是非线性的)且不存在着任何环的一种数据结构。没有结点的树称为空(null或empty)树。一棵非空的树包括一个根结点,还(很可能)......
  • Connecting the Hosts: Street-Level IP Geolocation with Graph Neural Networks论文
    ConnectingtheHosts:Street-LevelIPGeolocationwithGraphNeuralNetworksABSTRACT大概讲述了该论文的重要性,作者利用主机信息和邻居关系嵌入到图中来推断拓扑结......
  • LeetCode 1367. Linked List in Binary Tree
    原题链接在这里:https://leetcode.com/problems/linked-list-in-binary-tree/题目:Givenabinarytree root anda linkedlistwith head asthefirstnode. Ret......
  • CF375D Tree and Queries dsu on tree
    一颗以1为根树,每个点有颜色,每次询问求x子树内颜色出现次数>=k的数量。线段树合并很难处理,考虑dfuontree。直接的想法是开一个数量树状数组每次查一下1~k-1的前缀和来......
  • CF1624G MinOr Tree 题解
    CF1624GMinOrTree给定\(n\)个点,\(m\)条边,求在或运算小的最小生成树考虑二进制拆位,从高位玩往地位贪心,如果答案第\(i\)位可以为\(0\),后\(i-1\)取值无论是多少......
  • leetcode -- tree 4
    不同的二叉搜索树II递归#Definitionforabinarytreenode.#classTreeNode:#def__init__(self,val=0,left=None,right=None):#self.val=......
  • [ 数据结构 - C++]红黑树RBTree
    在上篇文章我们了解了第一种平衡二叉搜索树AVL树,我们知道AVL树是通过平衡因子来控制左右子树高度差,从而将二叉树变成一颗平衡二叉搜索树。本篇文章我们将要了解另外一种平衡......
  • New Year Tree
    NewYearTreeTranslation给出一棵\(n\)个节点的树,根节点为\(1\)。每个节点上有一种颜色\(c_i\)。\(m\)次操作。操作有两种:1uc:将以\(u\)为根的子树上的所有节点......