[SPOJ QTREE] Query on a tree (树链剖分)

本文详细介绍了树链剖分的概念及应用,通过实例讲解如何利用树链剖分解决树区间问题,实现高效的区间查询和更新操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接

SPOJ QTREE


题意

给出一个树,边带权,然后给出若干个询问,询问分QUERY a b和CHANGE i ti两种,前者查询节点a、b之间经过不重复路径的最大边权,后者改变第i边权值为ti,对每个QUERY操作输出相应结果。


题解

树链剖分的裸题。
沈阳网赛上吃了亏,补一下比较高级的数据结构,其实树链剖分并没想的那么复杂(对深搜做法而言)。
大致介绍一下树链剖分,和记录它的整个过程如下:
树链剖分是用来解决树区间问题的,这里的树区间指的是两节点之间的不重复路径经过的每条边权组成的区间,通过树链剖分可将这些边映射在线段树上,用以维护。
对树中的任意节点i,需要记录以下信息:
fa[i]、sz[i]、dep[i]、next[i]、idx[i]、top[i]
fa[i]为i节点的父节点
sz[i]为以i节点为根的子树的规模
dep[i]为i节点深度,根节点为1,向下深度递增
next[i]为i节点的子节点中sz最大的节点(多个取其一),即沿着重链的下一个节点
idx[i]为以i节点为下端点的边在线段树中的位置
top[i]为i节点所在“重链”的头节点
在第一次dfs中可处理出每个点的fa、sz、dep和next:

void dfs1(int u, int f, int d)
{
    fa[u] = f;
    dep[u] = d;
    sz[u] = 1;
    next[u] = 0;
    for(int i = 0, v; i < son[u].size(); i++)
    if((v = son[u][i]) != f) {
        dfs1(v, u, d + 1);
        sz[u] += sz[v];
        if(sz[next[u]] < sz[v])
            next[u] = v;
    }
}

对于一个节点u,它和next[u]的连边我们记为重边,和它其它子的连边为轻边,接下来的第二次dfs中,将重边串成一条重链,具体表现为链上的所有节点都有相同的top。
从根向下递归处理,top[1] = 1,对u节点,向其next[u]传递top[u]作为top,其它子节点v以v本身作为top,向下递归即可。在整个过程中要对每个节点设置idx,注意对每个节点先对其next[u]进行递归、再递归其它节点,因为我们需要一条重链上的idx是连续的,对应线段树上连续的一段:

int top[maxn], w[maxn], idx[maxn], cnt;
void dfs2(int u, int tp)
{
    top[u] = tp;
    idx[u] = ++cnt;
    if(!next[u]) return ;
    dfs2(next[u], tp);
    for(int i = 0, v; i < son[u].size(); i++)
    if((v = son[u][i]) != fa[u] && v != next[u]) {
        dfs2(v, v);
    }
}

这样树链就剖分完成,我们对w[idx[i]]建立线段树即可维护。
树链剖分的作用在于加速两个节点间区间的查询,如果两个节点a、b位于同一条重链,那么显然可以query(idx[next[a]], idx[b], 1, n, 1),如果a、b位于不同重链,就让二者向同一重链去靠拢,由于每次都是一条重链一条轻边这样的跨度,所以二者很快就到了同一重链上。
见网上说对任意节点a、b,二者靠拢经过的重链和轻边数均为logn级的,这样结合线段树的每次查询就是lognlogn,st表就是logn了,但是无法更改。

int qtree(int u, int v, int n)
{
    int topu = top[u], topv = top[v], ans = 0;
    while(topu != topv)
    {
        // printf("while()\n");
        if(dep[topu] < dep[topv]) { swap(topu, topv); swap(u, v); }
        ans = max(query(idx[topu], idx[u], 1, n, 1), ans);
        u = fa[topu];
        topu = top[u];
    }
    if(u == v) return ans;
    if(dep[u] > dep[v]) swap(u, v);
    ans = max(ans, query(idx[next[u]], idx[v], 1, n, 1));
    return ans;
}

代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define maxn (10010)
struct edge
{
    int a, b, c;
} e[maxn];
vector<int> son[maxn];
int fa[maxn], dep[maxn], sz[maxn], next[maxn];
void dfs1(int u, int f, int d)
{
    fa[u] = f;
    dep[u] = d;
    sz[u] = 1;
    next[u] = 0;
    for(int i = 0, v; i < son[u].size(); i++)
    if((v = son[u][i]) != f) {
        dfs1(v, u, d + 1);
        sz[u] += sz[v];
        if(sz[next[u]] < sz[v])
            next[u] = v;
    }
}
int top[maxn], w[maxn], idx[maxn], cnt;
void dfs2(int u, int tp)
{
    top[u] = tp;
    idx[u] = ++cnt;
    if(!next[u]) return ;
    dfs2(next[u], tp);
    for(int i = 0, v; i < son[u].size(); i++)
    if((v = son[u][i]) != fa[u] && v != next[u]) {
        dfs2(v, v);
    }
}
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
int seg[maxn << 2];
void build(int l, int r, int rt)
{
    if(l == r) { seg[rt] = w[l]; return; }
    int m = (l + r) >> 1;
    build(lson), build(rson);
    seg[rt] = max(seg[rt<<1], seg[rt<<1|1]);
}
void update(int x, int val, int l, int r, int rt)
{
    if(l == r) { seg[rt] = val; return; }
    int m = (l + r) >> 1;
    if(x <= m) update(x, val, lson);
    else update(x, val, rson);
    seg[rt] = max(seg[rt<<1], seg[rt<<1|1]);
}
int query(int L, int R, int l, int r, int rt)
{
    if(L <= l && r <= R) return seg[rt];
    int m = (l + r) >> 1, ret = -1;
    if(L <= m) ret = max(ret, query(L, R, lson));
    if(R > m) ret = max(ret, query(L, R, rson));
    return ret;
}
int qtree(int u, int v, int n)
{
    int topu = top[u], topv = top[v], ans = 0;
    while(topu != topv)
    {
        // printf("while()\n");
        if(dep[topu] < dep[topv]) { swap(topu, topv); swap(u, v); }
        ans = max(query(idx[topu], idx[u], 1, n, 1), ans);
        u = fa[topu];
        topu = top[u];
    }
    if(u == v) return ans;
    if(dep[u] > dep[v]) swap(u, v);
    ans = max(ans, query(idx[next[u]], idx[v], 1, n, 1));
    return ans;
}
int main()
{
    int t;
    cin >> t;
    while(t--)
    {
        int N;
        cin >> N;
        cnt = 0;
        for(int i = 1; i <= N; i++) son[i].clear();
        for(int i = 1; i <= N-1; i++)
        {
            scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].c);
            son[e[i].a].push_back(e[i].b);
            son[e[i].b].push_back(e[i].a);
        }
        dfs1(1, 0, 1);
        dfs2(1, 1);
        for(int i = 1; i <= N-1; i++)
        {
            if(dep[e[i].a] < dep[e[i].b]) swap(e[i].a, e[i].b);
            w[idx[e[i].a]] = e[i].c;
        }
        build(1, N, 1);
        char op[10];
        int a, b;
        while(scanf("%s", op) && op[0] != 'D')
        {
            scanf("%d%d", &a, &b);
            if(op[0] == 'Q')
                printf("%d\n", qtree(a, b, N));
            else
                update(idx[e[a].a], b, 1, N, 1);
        }
        if(t) putchar('\n');
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值