CF1628E Groceries in Meteor Town

#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;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值