树上最值路径 题解

题意简述

给你一棵 n 个结点的树,编号为 1n,求有多少路径 Path(uv),满足 u=max{xxPath(uv)}v=min{xxPath(uv)}

n2×106

题目分析

考虑从小到大的顺序枚举 u,去掉 max 的限制,因为 u 必然是之前的点中的 maxmin 从大到小扫同理。在加入 u 之前,这棵树是若干个联通块。u 和其相邻的联通块之中的任意一点 v,都能保证 u=max{xxPath(uv)}。我们考虑并查集缩点,每次在一棵新的树上,把代表联通块的点和 u 之间连一条边,并将其在并查集上的父亲设为 u。这实际上是一个重构树的过程,这个新树有一个很好的性质,那就是当且仅当 vu 在新树上的所有后代,使得 u=max{xxPath(uv)}。我们将一个奇怪的最值限定,变成了树上祖先后代的限定。我们将这棵树称作 max 树。

我们现在有了两棵重构树,分别满足 minmax 的限制。然后就要考虑如何统计答案了,也就是有多少对 (u,v),满足在 max 树上,uv 的祖先,在 min 树上,vu 的祖先。我们还是考虑枚举,在 min 树上枚举一个 u,查询有多少 min 树上 u 的祖先 v,满足在 max 树上 vu 的后代。祖先的限制可以用 dfs 栈的思想,用一个数据结构实时维护 dfs 栈中的元素。后者查询后代操作,可以用 dfs 序 + 树状数组区间查询。那么 dfs 栈就是树状数组的单点加操作。

时间复杂度:Θ(n(α(n)+logn))

代码

#include <cstdio>
#include <iostream>
using namespace std;
const int N = 2000010;
struct Graph {
struct node {
int to, nxt;
} edge[N << 1];
int tot, head[N];
inline void add(int u, int v) {
edge[++tot] = {v, head[u]};
head[u] = tot;
}
inline node & operator [] (int x) {
return edge[x];
}
} xym, yzh1, yzh2;
int n;
long long ans;
int fa[N];
int get(int x) {
return fa[x] == x ? x : fa[x] = get(fa[x]);
}
int tree[N];
inline void modify_add(int p) {
for (int i = p; i <= n; i += i & -i)
++tree[i];
}
inline void modify_sub(int p) {
for (int i = p; i <= n; i += i & -i)
--tree[i];
}
inline int query(int p) {
int res = 0;
for (int i = p; i; i &= i - 1)
res += tree[i];
return res;
}
int L[N], R[N], timer;
void dfs(int now) {
L[now] = ++timer;
for (int i = yzh1.head[now]; i; i = yzh1[i].nxt)
dfs(yzh1[i].to);
R[now] = timer;
}
void redfs(int now) {
ans += query(R[now]) - query(L[now] - 1);
modify_add(L[now]);
for (int i = yzh2.head[now]; i; i = yzh2[i].nxt)
redfs(yzh2[i].to);
modify_sub(L[now]);
}
signed main() {
#ifndef XuYueming
freopen("charity.in", "r", stdin);
freopen("charity.out", "w", stdout);
#endif
scanf("%d", &n);
for (int i = 1, f; i <= n; ++i) {
scanf("%d", &f), fa[i] = i;
if (f == 0) continue;
xym.add(i, f), xym.add(f, i);
}
for (int u = 2; u <= n; ++u) {
for (int i = xym.head[u]; i; i = xym[i].nxt) {
int v = xym[i].to;
if (v > u) continue;
yzh2.add(u, v = get(v));
fa[v] = u;
}
}
for (int i = 1; i <= n; ++i) fa[i] = i;
for (int u = n - 1; u >= 1; --u) {
for (int i = xym.head[u]; i; i = xym[i].nxt) {
int v = xym[i].to;
if (v < u) continue;
yzh1.add(u, v = get(v));
fa[v] = u;
}
}
dfs(1), redfs(n), printf("%lld", ans);
return 0;
}
posted @   XuYueming  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示