POJ 1986 LCA + RMQ + BIT / 倍增

题意

传送门 POJ 1986

题解
LCA + RMQ + BIT

将树转为从根 D F S DFS DFS 标号后得到的序列,使用基于 R M Q RMQ RMQ 的算法求 L C A LCA LCA,用 B I T BIT BIT 维护边权和。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 40010
#define maxm (1 << 18)
struct edge
{
    int v, w;
    edge() {}
    edge(int v, int w) : v(v), w(w) {}
};
int N, M, K, root, id[maxn], vs[maxn * 2], dep[maxn * 2];
vector<edge> G[maxn];
int bit_n, bit[maxm], st_n, st[maxm];

void add(int i, int x)
{
    while (i <= bit_n)
    {
        bit[i] += x;
        i += i & -i;
    }
}

int sum(int i)
{
    int s = 0;
    while (i > 0)
    {
        s += bit[i];
        i -= i & -i;
    }
    return s;
}

void rmq_init(int k, int l, int r)
{
    if (r - l == 1)
    {
        st[k] = l;
    }
    else
    {
        int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (l + r) >> 1;
        rmq_init(chl, l, m);
        rmq_init(chr, m, r);
        st[k] = dep[st[chl]] > dep[st[chr]] ? st[chr] : st[chl];
    }
}

int query(int a, int b, int k, int l, int r)
{
    if (r <= a || b <= l)
    {
        return st_n;
    }
    if (a <= l && r <= b)
    {
        return st[k];
    }
    int chl = (k << 1) + 1, chr = (k << 1) + 2, m = (l + r) >> 1;
    int i = query(a, b, chl, l, m), j = query(a, b, chr, m, r);
    return dep[i] > dep[j] ? j : i;
}

void dfs(int v, int p, int d, int &k)
{
    id[v] = k, vs[k] = v, dep[k++] = d;
    for (int i = 0; i < G[v].size(); i++)
    {
        edge &e = G[v][i];
        if (e.v != p)
        {
            add(k, e.w);
            dfs(e.v, v, d + 1, k);
            vs[k] = v, dep[k++] = d;
            add(k, -e.w);
        }
    }
}

void init(int V)
{
    bit_n = (V - 1) * 2, st_n = bit_n + 1;
    int k = 0;
    dfs(root, -1, 0, k);
    dep[st_n] = inf;
    rmq_init(0, 0, st_n);
}

int lca(int u, int v)
{
    return vs[query(min(id[u], id[v]), max(id[u], id[v]) + 1, 0, 0, st_n)];
}

int main()
{
    scanf("%d%d", &N, &M);
    for (int i = 0; i < M; i++)
    {
        int u, v, w;
        char d;
        scanf("%d%d%d %c", &u, &v, &w, &d);
        --u, --v;
        G[u].push_back(edge(v, w));
        G[v].push_back(edge(u, w));
    }
    root = 0;
    init(N);
    scanf("%d", &K);
    for (int i = 0; i < K; i++)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        --u, --v;
        int p = lca(u, v);
        printf("%d\n", sum(id[u]) + sum(id[v]) - 2 * sum(id[p]));
    }
    return 0;
}
LCA + 倍增

预处理出每个顶点向根节点走 2 k 2^k 2k 步所到的节点与路径上的边权和,便可以 O ( l o g N ) O(logN) O(logN) 计算 L C A LCA LCA 的同时求出路径和。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
#define maxn 40010
#define maxl 17
struct edge
{
    int v, w;
    edge() {}
    edge(int v, int w) : v(v), w(w) {}
};
int N, M, K, root, dep[maxn * 2], par[maxl][maxn], dis[maxl][maxn];
vector<edge> G[maxn];

void dfs(int v, int p, int d, int c)
{
    par[0][v] = p, dis[0][v] = c, dep[v] = d;
    for (int i = 0; i < G[v].size(); i++)
    {
        edge &e = G[v][i];
        if (e.v != p)
        {
            dfs(e.v, v, d + 1, e.w);
        }
    }
}

void init(int V)
{
    dfs(root, -1, 0, -1);
    for (int k = 0; k + 1 < maxl; k++)
    {
        for (int v = 0; v < V; v++)
        {
            par[k + 1][v] = par[k][v] == -1 ? -1 : par[k][par[k][v]];
            dis[k + 1][v] = dis[k][v] == -1 ? -1 : dis[k][par[k][v]] + dis[k][v];
        }
    }
}

int lca(int u, int v)
{
    if (dep[u] > dep[v])
    {
        swap(u, v);
    }
    int d = 0;
    for (int k = 0; k < maxl; k++)
    {
        if ((dep[v] - dep[u]) >> k & 1)
        {
            d += dis[k][v], v = par[k][v];
        }
    }
    if (u == v)
    {
        return d;
    }
    for (int k = maxl - 1; k >= 0; k--)
    {
        if (par[k][u] != par[k][v])
        {
            d += dis[k][u] + dis[k][v], u = par[k][u], v = par[k][v];
        }
    }
    return d + dis[0][u] + dis[0][v];
}

int main()
{
    scanf("%d%d", &N, &M);
    for (int i = 0; i < M; i++)
    {
        int u, v, w;
        char d;
        scanf("%d%d%d %c", &u, &v, &w, &d);
        --u, --v;
        G[u].push_back(edge(v, w));
        G[v].push_back(edge(u, w));
    }
    root = 0;
    init(N);
    scanf("%d", &K);
    for (int i = 0; i < K; i++)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        --u, --v;
        printf("%d\n", lca(u, v));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值