#include <bits/stdc++.h>
using namespace std;
// 定义常量和宏
const int maxn = 3e5 + 10;
const int inf = 1e9;
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)
// 存储原始边的结构体
struct edge {
int u, v, w;
} e[maxn];
// 并查集相关
int par[maxn << 1]; // 并查集数组,大小为2n
inline int find(int x) {
return x == par[x] ? x : par[x] = find(par[x]);
}
// Kruskal重构树相关
int ch[2][maxn << 1]; // 重构树中每个节点的左右孩子
int wt[maxn << 1]; // 重构树中每个节点的权值(对于新建节点,权值为原边权)
int fa[30][maxn << 1]; // 倍增数组,用于LCA计算
int dfn[maxn << 1]; // DFS序
int idfn[maxn << 1]; // DFS序对应的原节点编号
int dep[maxn << 1]; // 节点深度
int dfs_clock; // DFS时间戳
int tot; // 重构树节点总数
// 构建Kruskal重构树
void krt_build(int n) {
// 按边权从小到大排序
sort(e + 1, e + n, [&](edge &x, edge &y) { return x.w < y.w; });
// 初始化并查集,前n个节点是原图的n个点
rep(i, 1, n << 1) par[i] = i;
tot = n; // 初始有n个节点,新建节点从n+1开始
// 构建重构树
rep(i, 1, n - 1) {
int u = find(e[i].u), v = find(e[i].v), w = e[i].w;
// 新建节点,权值为当前边权
par[u] = par[v] = fa[0][u] = fa[0][v] = ++tot;
ch[0][tot] = u; // 左孩子
ch[1][tot] = v; // 右孩子
wt[tot] = w; // 设置节点权值
}
}
// DFS预处理:计算DFS序、深度,构建倍增数组
void dfs(int cur) {
dfn[cur] = ++dfs_clock; // 记录DFS序
idfn[dfs_clock] = cur; // 记录DFS序对应的节点
dep[cur] = dep[fa[0][cur]] + 1; // 计算深度
// 预处理倍增数组
rep(i, 1, 25) fa[i][cur] = fa[i - 1][fa[i - 1][cur]];
// 递归处理左右孩子
rep(i, 0, 1) if (ch[i][cur]) dfs(ch[i][cur]);
}
// LCA计算函数
int lca(int x, int y) {
// 确保x是深度较大的节点
if (dep[x] < dep[y]) swap(x, y);
// 将x提升到与y同一深度
per(i, 25, 0) if (dep[fa[i][x]] >= dep[y]) x = fa[i][x];
// 如果已经是同一个节点,直接返回
if (x == y) return x;
// 同时向上跳,直到父节点相同
per(i, 25, 0) if (fa[i][x] != fa[i][y]) x = fa[i][x], y = fa[i][y];
return fa[0][x];
}
// 线段树类,用于维护白点(开门的杂货店)的DFS序信息
struct segTree {
int mx[maxn << 2]; // 区间内白点的最大DFS序
int mn[maxn << 2]; // 区间内白点的最小DFS序
int smx[maxn << 2]; // 区间内所有点的最大DFS序(不考虑开关状态)
int smn[maxn << 2]; // 区间内所有点的最小DFS序(不考虑开关状态)
int tag[maxn << 2]; // 懒标记:-1未标记,0关闭,1开启
// 应用标记到节点
void as(int p, int v) {
v ? (mx[p] = smx[p], mn[p] = smn[p]) : (mx[p] = 0, mn[p] = inf);
}
// 下传懒标记
void spread(int p) {
register int ls = p << 1, rs = ls | 1;
as(ls, tag[p]), as(rs, tag[p]), tag[ls] = tag[rs] = tag[p], tag[p] = -1;
}
// 构建线段树
void build(int p, int lp, int rp) {
tag[p] = -1;
mx[p] = 0, mn[p] = inf; // 初始没有白点
if (lp == rp) {
// 叶子节点:存储原节点的DFS序
smx[p] = smn[p] = dfn[lp];
return;
}
register int mid = (lp + rp) >> 1, ls = p << 1, rs = ls | 1;
build(ls, lp, mid), build(rs, mid + 1, rp);
// 合并子节点信息
smx[p] = max(smx[ls], smx[rs]);
smn[p] = min(smn[ls], smn[rs]);
}
// 区间赋值操作(开启或关闭杂货店)
void assign(int p, int lp, int rp, int l, int r, int v) {
if (l <= lp && rp <= r) {
// 整个区间被覆盖,直接应用标记
v ? (mn[p] = smn[p], mx[p] = smx[p]) : (mn[p] = inf, mx[p] = 0);
tag[p] = v;
return;
}
register int mid = (lp + rp) >> 1, ls = p << 1, rs = ls | 1;
// 下传标记
if (~tag[p]) spread(p);
// 递归处理左右子树
if (l <= mid) assign(ls, lp, mid, l, r, v);
if (r > mid) assign(rs, mid + 1, rp, l, r, v);
// 合并子节点信息
mx[p] = max(mx[ls], mx[rs]);
mn[p] = min(mn[ls], mn[rs]);
}
// 查询当前所有白点的DFS序最小值和最大值
void qry(int &v1, int &v2) {
v1 = mn[1]; // 最小DFS序
v2 = mx[1]; // 最大DFS序
}
} s;
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL);
int n, q, u, v, w;
cin >> n >> q;
// 读入边
rep(i, 1, n - 1) {
cin >> u >> v >> w;
e[i] = {u, v, w};
}
// 构建Kruskal重构树
krt_build(n);
// DFS预处理重构树
dfs(tot); // 从根节点(最后一个新建节点)开始DFS
// 构建线段树,维护原图节点(1~n)的DFS序信息
s.build(1, 1, n);
int t, l, r, x;
rep(qr, 1, q) {
cin >> t;
if (t == 1 || t == 2) {
// 类型1:开启[l,r]的杂货店;类型2:关闭[l,r]的杂货店
cin >> l >> r;
s.assign(1, 1, n, l, r, t == 1 ? 1 : 0);
} else {
// 类型3:查询从x到任意开着的杂货店路径上的最大危险等级
cin >> x;
int mn, mx;
s.qry(mn, mx); // 获取所有白点的DFS序最小值和最大值
// 如果没有白点,或者只有x本身是白点且没有其他白点
if (mn == inf || (mn == dfn[x] && mx == mn)) {
cout << -1 << endl;
} else {
// 关键性质:所有白点的LCA = DFS序最小和最大的两个白点的LCA
// x与所有白点的LCA = x与(所有白点的LCA)的LCA
// 路径最大危险等级 = 该LCA节点的权值
cout << wt[lca(idfn[mn], lca(idfn[mx], x))] << endl;
}
}
}
return 0;
}
CF1628E Groceries in Meteor Town
最新推荐文章于 2025-12-12 15:43:51 发布

10

被折叠的 条评论
为什么被折叠?



