[POJ2763] Housewife Wind 题解

[POJ2763] Housewife Wind 题解

[POJ2763] Housewife Wind

题目简述

给一棵有 \(n\) 个点的带权树,\(q\) 次询问,每次两种操作。

  1. 查询树上两点最短距离
  2. 修改一条边的边权

\(n \le 10^5 , q \le 10^5\)

想法

如果只看操作一,明显可以用 \(\text{LCA}\) 求解,具体步骤是:

  1. 预处理所有点到根的距离 \(dist_i\)

  2. 输入 \(u, v\),求得 \(\text{LCA}(u, v)\),距离即为

    \[dist_u + dist_v - 2dist_{LCA} \]

但是此时多了一个修改边权的操作,考虑如果修改一条边的边权 ,哪些点到根的距离会发生改变。

答:这条边通向的节点的子树

但是如果暴力修改明显不可接受,这里有一种常见的trick:

通过 \(\text{Dfs}\) 序将整棵树转变为一串序列,利用数据结构方便地维护序列中的区间信息。

具体而言就是把 \(\text{Dfs}\) 序访问的节点依次记录下来,构成一个访问序列。

对于这题而言,我们需要在序列区间上进行 区间修改,改变他们到根节点的距离。然后进行 单点查询,分别查询 \(u, v, lca\) 到根的距离。

对于这个 区间修改,单点查询 的问题,可以用 BIT 或者线段树简单维护,这题卡得比较严,我采用了常数较小、较容易实现的 BIT。

// Problem: Housewife Wind
// Contest: POJ - POJ Monthly--2006.02.26
// URL: http://poj.org/problem?id=2763
// Memory Limit: 65 MB
// Time Limit: 4000 ms
// Author: Moyou
// Copyright (c) 2022 Moyou All rights reserved.
// Date: 2023-01-18 11:42:22

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;

const int N = 2e5 + 10;

int n, q, s;

struct edge
{
    int v, w, id;
};

vector<edge> g[N];

int lpos[N << 2], rpos[N << 2], id[N << 2], idx, val[N << 2];

struct BIT
{
    int tr[N];
    int lowbit(int x)
    {
        return x & -x;
    }

    void update(int i, int c) // 位置x加c
    {
        for (; i <= (idx); i += lowbit(i))
            tr[i] += c;
    }

    int query(int i) // 返回前x个数的和
    {
        int res = 0;
        for (; i; i &= i - 1) // 等价于i -= lowbit(i)
            res += tr[i];
        return res;
    }
} t; // 树状数组板

int f[N][23], depth[N];
void get_depth(int u, int fa, int c) // 倍增LCA板 + 维护序列信息
{
    depth[u] = depth[fa] + 1, f[u][0] = fa;

    for (int i = 1; i <= 20; i++)
        f[u][i] = f[f[u][i - 1]][i - 1];
    for(int j = 0; j < g[u].size(); j ++)
    {
        int i = g[u][j].v, c = g[u][j].w, x = g[u][j].id;
        if (i == fa)
            continue;
        lpos[x] = id[i] = ++ idx; // 记录边与点在序列中的编号
        get_depth(i, u, c);
        rpos[x] = ++ idx;
    }
}

int Lca(int a, int b) // 倍增
{
    if (depth[a] > depth[b])
        swap(a, b);
    for (int i = 20; i >= 0; i--)
        if (depth[f[b][i]] >= depth[a])
            b = f[b][i];
    if (a == b)
        return a;
    for (int i = 20; i >= 0; i--)
        if (f[b][i] != f[a][i])
            b = f[b][i], a = f[a][i];
    return f[a][0];
}

edge make(int a, int b, int c)
{
    edge tmp;
    tmp.v = a, tmp.w = b, tmp.id = c;
    return tmp;
}

inline char get_char() // 加一点小小的卡常🤏
{
    static char buf[1000000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++;
}

inline int read()
{
    int x = 0;
    char ch = get_char();
    while (ch < '0' || ch > '9')
        ch = get_char();
    while (ch <= '9' && ch >= '0')
        x = (x << 1) + (x << 3) + (ch ^ 48), ch = get_char();
    return x;
}

int main()
{
    n = read(), q = read(), s = read();
    for (int i = 1; i < n; i++)
    {
        int a = read(), b = read(), w = read();
        g[a].push_back(make(b, w, i));
        g[b].push_back(make(a, w, i));
        val[i] = w;
    }

    get_depth(1, 0, 0); 
    
    for(int i = 1; i < n; i ++) // 差分树状数组
        t.update(lpos[i], val[i]), t.update(rpos[i], -val[i]);

    while (q--)
    {
        int op = read();
        if (op)
        {
            int u = read(), tmp = read();
            int l = lpos[u], r = rpos[u];
            t.update(l, tmp - val[u]), t.update(r, val[u] - tmp); // 因为是修改而不是增加需要改变一下
            val[u] = tmp;
        }
        else
        {
            int b = read();
            int lca = Lca(s, b);
            printf("%d\n", t.query(id[s]) + t.query(id[b]) - 2 * t.query(id[lca]));
            s = b; // 不要忘了结合题目,出发点是改变的
        }
    }
// cout << ' ';
    return 0;
}
posted @ 2023-01-18 13:49  MoyouSayuki  阅读(59)  评论(0编辑  收藏  举报
:name :name