洛谷题单指南-图论之树-P3313 [SDOI2014] 旅行

原题链接:https://www.luogu.com.cn/problem/P3313

题意解读:一棵树的节点有两个属性:评级w和信仰c,树上支持4种操作:1.单点修改评级 2.单点修改信仰 3.查询路径上和起点信仰相同节点的评级之和 4.查询路径上和起点信仰相同节点的最大评级。

解题思路:显然也是一道树链剖分+线段树的问题,关键在于如何构建线段树。

由于每次查询必须限制同一种信仰的节点,可以考虑按照不同的信仰构建多棵线段树,信仰i对应的线段树根节点设为root[i]。

如果完整构建多棵线段树,空间显然撑不住,又可以借助动态开点大法,关于动态开点可以翻看之前的题解:

https://www.cnblogs.com/jcwy/p/18673073

https://www.cnblogs.com/jcwy/p/18640370

剩下的内容就是常规的树链剖分、线段树相关操作。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;

int w[N], c[N];
vector<int> g[N];
int son[N], siz[N], fa[N], depth[N], top[N], dfn[N], rk[N], cnt;
struct Node
{
    int L, R;
    int sum; //区间w值之和
    int maxw; //区间最大w值
} tr[N * 80];
int root[N], idx; //root[i]表示c值为i的权值线段树根节点
int n, m;

void dfs1(int u, int p, int d)
{
    depth[u] = d;
    fa[u] = p;
    siz[u] = 1;
    for(auto v : g[u])
    {
        if(v == p) continue;
        dfs1(v, u, d + 1);
        siz[u] += siz[v];
        if(siz[v] > siz[son[u]]) son[u] = v;
    }
}

void dfs2(int u, int t)
{
    top[u] = t;
    dfn[u] = ++cnt;
    rk[cnt] = u;
    if(son[u]) dfs2(son[u], t);
    for(auto v : g[u])
    {
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

void pushup(Node &root, Node &left, Node &right)
{
    root.sum = left.sum + right.sum;
    root.maxw = max(left.maxw, right.maxw);
}

void pushup(int u)
{
    pushup(tr[u], tr[tr[u].L], tr[tr[u].R]);
}

//将根为u的线段树中pos位置的w权值设置为val
int update(int u, int l, int r, int pos, int val)
{
    if(!u) u = ++idx;
    if(l == r)
    {
        tr[u].sum = val;
        tr[u].maxw = val;
        return u;
    }
    int mid = l + r >> 1;
    if(pos <= mid) tr[u].L = update(tr[u].L, l, mid, pos, val);
    else tr[u].R = update(tr[u].R, mid + 1, r, pos, val);
    pushup(u);
    return u;
}

//查询根为u的线段树中L~R区间的sum和maxw
Node query(int u, int l, int r, int L, int R)
{
    if(l >= L && r <= R) return tr[u];
    else if(l > R || r < L) return {0, 0, 0 ,0};
    else 
    {
        int mid = l + r >> 1;
        Node res = {0, 0, 0, 0};
        Node left = query(tr[u].L, l, mid, L, R);
        Node right = query(tr[u].R, mid + 1, r, L, R);
        pushup(res, left, right);
        return res;
    }
}

//查询u到v的路径上信仰为c的sum和maxw
Node queryPath(int c, int u, int v)
{
    Node res = {0, 0, 0, 0};
    while(top[u] != top[v])
    {
        if(depth[top[u]] < depth[top[v]]) swap(u, v);
        Node tmp = query(root[c], 1, n, dfn[top[u]], dfn[u]);
        pushup(res, tmp, res);
        u = fa[top[u]];
    }
    if(depth[u] > depth[v]) swap(u, v);
    Node tmp = query(root[c], 1, n, dfn[u], dfn[v]);
    pushup(res, tmp, res);
    return res;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> w[i] >> c[i];
    for(int i = 1; i < n; i++) 
    {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs1(1, 0, 1);
    dfs2(1, 1);
    for(int i = 1; i <= n; i++) root[c[i]] = update(root[c[i]], 1, n, dfn[i], w[i]);
    while (m--)
    {   
        string op;
        int x, y;
        cin >> op >> x >> y;
        if(op == "CC")
        {
            //将root[c[x]]中dfn[x]位置的权值设置为0
            root[c[x]] = update(root[c[x]], 1, n, dfn[x], 0);
            //将root[y]中dfn[x]位置的权值设置为w[x]
            root[y] = update(root[y], 1, n, dfn[x], w[x]);
            c[x] = y; 
        }
        else if(op == "CW")
        {
            //将root[c[x]]中dfn[x]位置的权值设置为y
            root[c[x]] = update(root[c[x]], 1, n, dfn[x], y);
            w[x] = y;
        }
        else if(op == "QS")
        {
            Node res = queryPath(c[x], x, y);
            cout << res.sum << endl;
        }
        else if(op == "QM")
        {
            Node res = queryPath(c[x], x, y);
            cout << res.maxw << endl;
        }
    }
    
}

 

posted @ 2025-03-28 16:10  hackerchef  阅读(48)  评论(0)    收藏  举报