图论-树上问题-树上启发式合并
树上启发式合并
前言
我们引入如下问题
给一棵根为1的树,每次询问子树颜色种类数。
想一个简单的方法,即定义 \(f[u][c]\) 表示以 \(u\) 为根,颜色 \(c\) 的出现次数。
不难发现,这种思路的时间与空间复杂度为 \(O(n^2)\) 所以本文章将介绍一种思路以解决这一类的问题。
介绍
启发式算法
启发式算法是基于人类的经验和直观感觉,对一些算法的优化。
举个例子,并查集,对于两个大小不一样的集合,我们将小的集合合并到大的集合中,优于将大的集合合并到小的集合中,因为并查集的特性,我们希望树的高度尽可能小,以查询其父节点
树上启发式合并
对于维护树上信息,需要将子节点维护的信息全部合并到父亲节点这一类型的问题时,由于空间和时间的限制,我们不能同时维护每一个节点的数据,所以对于上传数据的时候可以保留一部分信息来节省时间。不难想到,我们应保留数据更大即树的大小更大的部分,也就是树链剖分中常常提到的重链。此时就可以省去计算最重子树的数据,即时间复杂度可以到 \(O(nlogn)\) 空间可以达到 \(O(n)\)
具体实现:
- 每次先进入轻节点,递归他们。
- 进入重节点,递归他。
- 统计轻节点,并添加信息。
- 统计重节点,并添加信息。
- 若当前点是轻子树,删除当前节点所有信息。
由于遍历轻节点时会删去部分数据,所以在部分题目下会影响答案,所以应先遍历轻节点,再遍历重节点。
代码
这里给出引入中问题的代码
#include <bits/stdc++.h> #define int long long using namespace std; struct node{int to,next,w;}e[500010];int head[100010];int numEdge = 0;void add(int u, int v, int w){e[++numEdge].next = head[u];e[numEdge].to = v;e[numEdge].w = w;head[u] = numEdge;} int col[100010],n = 0,m = 0, siz[100010], son[100010], tt = 0, dfn[100010], maxdfn[100010], nfd[100010], tot = 0, cnt[100010], mx = 0, ctc = 0; void dfs(int u, int fa){ siz[u] = 1; dfn[u] = ++ tt; nfd[tt] = u; for(int i = head[u]; i; i = e[i].next){ int v = e[i].to; if(v == fa){ continue; } dfs(v, u); siz[u] += siz[v]; if(siz[v] > siz[son[u]]){ son[u] = v; } } maxdfn[u] = tt; } void adds(int u, int fa){ if(cnt[col[u]] == 0){ ctc ++; } cnt[col[u]] ++; } void del(int u, int fa){ cnt[col[u]] --; if(cnt[col[u]] == 0){ ctc --; } } int ans[100010]; void dfs2(int u, int fa, bool flg){ for(int i = head[u]; i; i = e[i].next){ int v = e[i].to; if(v != fa && v != son[u]){ dfs2(v, u, 0); } } if(son[u]){ dfs2(son[u], u, 1); } for(int i = head[u]; i; i = e[i].next){ int v = e[i].to; if(v != fa && v != son[u]){ for(int i = dfn[v]; i <= maxdfn[v]; i++){ adds(nfd[i], u); } } } adds(u, fa); ans[u] = ctc; if(!flg){ for(int i = dfn[u]; i <= maxdfn[u]; i++){ del(nfd[i], u); } } } signed main(){ cin >> n; for(int i = 1; i < n; i++){ int u = 0; int v = 0; cin >> u >> v; add(u, v, 1); add(v, u, 1); } for(int i = 1; i <= n; i++){ cin >> col[i]; } dfs(1, 0); dfs2(1, 0, 0); int m = 0; cin >> m; while(m --){ int op = 0; cin >> op; cout << ans[op] << "\n"; } return 0; }
本文作者:GraphTheory
本文链接:https://www.cnblogs.com/GraphTheory/p/18719891
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步