20250706 haosen 树论总结
20250706 haosen 树论总结
CF516D Drazil and Morning Exercise

首先fx可以直接求出来,这个是显然的。
我们发现,fx最小的点一定在直径上,具体地,在直径的中点。
我们以他为根,我们就发现,fi随着树的深度递增。
那就可以直接双指针跑一下即可。
在f值从大到小排序后的节点序列上做双指针,维护若干个合法连通快,按f降序插入,清洗连通块使得满足差条件,然后合并连通块(当前点寻找up,也就是相邻的更小的),统计答案。
这个比较难理解,可以手摸一下。
CF1628E Groceries in Meteor Town
给定 N 个点的树,起初每个节点都是黑色。
三种操作:
把下标为 [l,r] 的点染成白色;
把下标为 [l,r] 的点染成黑色;
询问从节点 x 出发到达任意一个白色节点的简单路径上经过的边,最大可能的权值。不存在则输出-1.
考虑Kruskal重构树
于是查询操作就转化成了重构树上, x 点与所有白点的 lca 的权值。
你可以直接拍到dfn序上,用线段树维护区间最大和最小dfn,这就是答案。
这题我得放一下代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
const int INF = 1e9;
int n, q;
// 链式前向星
struct Edge {
int to, next;
} e[N << 2];
int head[N << 1], ecnt;
void add_edge(int u, int v) {
e[++ecnt] = {v, head[u]};
head[u] = ecnt;
}
// Kruskal重构树
namespace KruskalTree {
struct EDGE {
int u, v, w;
EDGE(int u = 0, int v = 0, int w = 0) : u(u), v(v), w(w) {}
bool operator<(const EDGE &other) const {
return w < other.w;
}
};
EDGE edges[N];
int pcnt; // 新增节点计数
int fa[N << 1];
int val[N << 1]; // 重构树节点权值
int find(int x) {
return x != fa[x] ? fa[x] = find(fa[x]) : x;
}
void add(int u, int v, int w, int i) {
edges[i] = EDGE(u, v, w);
}
void build() {
// 初始化并查集
for (int i = 1; i <= (n << 1); i++) {
fa[i] = i;
}
// 按边权排序
sort(edges + 1, edges + n);
// 构建重构树
for (int i = 1; i < n; i++) {
int x = find(edges[i].u);
int y = find(edges[i].v);
int newNode = n + (++pcnt);
fa[x] = fa[y] = newNode;
add_edge(newNode, x);
add_edge(newNode, y);
val[newNode] = edges[i].w;
}
}
}
// LCA和DFS序
namespace LCA {
int dfncnt;
int dfn[N << 1]; // 节点对应的DFS序
int idfn[N << 1]; // DFS序对应的节点
int dep[N << 1];
int lg[N << 1];
int f[N << 1][21];
void dfs(int u, int fa) {
dfn[u] = ++dfncnt;
idfn[dfncnt] = u;
f[u][0] = fa;
dep[u] = dep[fa] + 1;
for (int i = 1; i <= lg[dep[u]]; i++) {
f[u][i] = f[f[u][i-1]][i-1];
}
for (int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if (!dfn[v]) {
dfs(v, u);
}
}
}
int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = lg[dep[x]]; i >= 0; i--) {
if (dep[f[x][i]] >= dep[y]) {
x = f[x][i];
}
}
if (x == y) return x;
for (int i = lg[dep[x]]; i >= 0; i--) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
void init() {
// 预处理log2
for (int i = 1; i <= (n << 1); i++) {
lg[i] = lg[i-1] + ((1 << lg[i-1]) == (i >> 1));
}
// 从根节点开始DFS
dfs(n + KruskalTree::pcnt, 0);
}
}
// 线段树维护区间开放商店的DFS序范围
namespace SegmentTree {
#define lc(i) (i << 1)
#define rc(i) (i << 1 | 1)
struct Range {
int maxn, minn;
Range(int maxn = -INF, int minn = INF) : maxn(maxn), minn(minn) {}
void update(int x) {
maxn = max(maxn, x);
minn = min(minn, x);
}
};
struct Node {
int l, r, tag; // tag: 0-无标记, 1-全开, 2-全关
Range val, all; // val-开放商店范围, all-所有节点范围
Node(int l = 0, int r = 0, int tag = 0) : l(l), r(r), tag(tag) {}
};
Node tr[N << 2];
Range merge(const Range &a, const Range &b) {
return Range(max(a.maxn, b.maxn), min(a.minn, b.minn));
}
void apply(int i, int tag) {
tr[i].tag = tag;
if (tag == 1) {
tr[i].val = tr[i].all; // 开放所有商店
} else if (tag == 2) {
tr[i].val = Range(-INF, INF); // 关闭所有商店
}
}
void push_down(int i) {
if (tr[i].tag) {
apply(lc(i), tr[i].tag);
apply(rc(i), tr[i].tag);
tr[i].tag = 0;
}
}
void push_up(int i) {
tr[i].val = merge(tr[lc(i)].val, tr[rc(i)].val);
}
void build(int l, int r, int i = 1) {
tr[i] = Node(l, r);
if (l == r) {
tr[i].all.update(LCA::dfn[l]);
return;
}
int mid = (l + r) >> 1;
build(l, mid, lc(i));
build(mid + 1, r, rc(i));
tr[i].all = merge(tr[lc(i)].all, tr[rc(i)].all);
}
void update(int l, int r, int tag, int i = 1) {
if (tr[i].l > r || tr[i].r < l) return;
if (tr[i].l >= l && tr[i].r <= r) {
apply(i, tag);
return;
}
push_down(i);
update(l, r, tag, lc(i));
update(l, r, tag, rc(i));
push_up(i);
}
Range query() {
return tr[1].val;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> q;
// 读入边并构建Kruskal重构树
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
KruskalTree::add(u, v, w, i);
}
KruskalTree::build();
LCA::init();
SegmentTree::build(1, n);
// 处理查询
for (int i = 0; i < q; i++) {
int type;
cin >> type;
if (type == 1 || type == 2) {
int l, r;
cin >> l >> r;
SegmentTree::update(l, r, type);
} else {
int x;
cin >> x;
auto range = SegmentTree::query();
range.update(LCA::dfn[x]);
if (range.maxn == range.minn) {
// 只有查询点本身开放或没有开放的商店
cout << -1 << "\n";
} else {
int lca_node = LCA::lca(LCA::idfn[range.maxn], LCA::idfn[range.minn]);
if (x == lca_node) {
cout << -1 << "\n";
} else {
cout << KruskalTree::val[lca_node] << "\n";
}
}
}
}
return 0;
}
CF566E Restoring Map
有一棵 n 个点的树,你不知道这棵树的边是怎么连的。
你得到了 n 条关于每个点信息,每条信息记录了距离某一个点 ≤2 的所有点。
但你不知道每条信息具体是哪个点的。
你需要构造一棵满足这些信息的树。
发现
非叶子节点x,y之间有边,当且仅当有两个集合(这里千万别理解错了,不然题就做不了了)交集为x,y,于是可以得到所有非叶节点之间的连边
我们知道每个点是否是叶子节点,接下来考虑如何找到叶子节点在树中的父亲。
显然,对于每个叶子节点,在所有包含它的集合中,节点数最少的集合一定就是此叶子节点的集合。因此我们可以确定每个叶子节点对应的集合是哪个。
一个叶子节点的集合中去掉所有叶子节点等于其父亲的连边集合
其他边界条件可以直接特判。
浙公网安备 33010602011771号