1830 路径交【LCA+线段树】

14 篇文章 0 订阅
11 篇文章 0 订阅

题目链接 51 nod 1830 路径交


  首先,理解一下题意,题目是给出一棵N个点的树,然后给出M条边,接着Q次询问,每次询问M条边中的第L条边到第R条边中有多长的边是与每条边都有交集的,记住是第L条到第R条的每一条都要交,而不是只要两两有交集就可以了。

  理清楚题意之后,我们先来看一下两条边相交得到的边是哪条边?

  譬如说我们有两条(u, v)边。

我们想知道(u1, v1)(u2, v2)的交边长度,如图可以很清楚的看出,是(u1, v2)这条边,然后也可以看作是(lca(u1, u2),\ lca(v1, v2))

我们不妨取任意两两组合形成的lca结点,那么就会有四个lca结点(因为这里求LCA次数较多,不妨使用欧拉序+RMQ的方法O(N log(N))预处理加上O(1)查询),为了保证相交,我们取深度最深的两个lca结点,如图:

我们选取lca1与lca2

  那么,如果没有交集的时候会发生什么事情呢?

他们的四个lca会在同一个点,也就是说当没有交边的时候,他们的lca就是一个点了。

  于是乎,我们利用这样的方法确定了交边的两个端点,而当线从两个求交边变成多个求共同交边的时候呢?我们可以利用已求得的交边去与交边继续交的形式,我们继续去求交边。这句话就相当于是A \cap B \cap C \cap D = (A \cap B) \cap (C \cap D)

  这样,我们就可以用线段树来做这样的一个区间维护了。

题目中还有一处不严谨的地方就是,题目给出的并非确定根的(没说1是根结点),所以我们依然要用无向边来解决。

最后推荐交题使用C++而不是C++11,会疯狂CE。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <bitset>
//#include <unordered_map>
//#include <unordered_set>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define Big_INF 0x3f3f3f3f3f3f3f3f
#define eps 1e-6
#define HalF (l + r)>>1
#define lsn rt<<1
#define rsn rt<<1|1
#define Lson lsn, l, mid
#define Rson rsn, mid+1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define myself rt, l, r
#define MP(a, b) make_pair(a, b)
using namespace std;
typedef unsigned long long ull;
typedef unsigned int uit;
typedef long long ll;
const int maxN = 5e5 + 7;
int N, M, Q, head[maxN], cnt, LOG_2[maxN << 1];
struct Eddge
{
    int nex, to; ll val;
    Eddge(int a=-1, int b=0, ll c=0):nex(a), to(b), val(c) {}
} edge[maxN << 1];
inline void addEddge(int u, int v, int w)
{
    edge[cnt] = Eddge(head[u], v, w);
    head[u] = cnt++;
}
inline void _add(int u, int v, int w) { addEddge(u, v, w); addEddge(v, u, w); }
struct Grand_Father
{
    int deep[maxN], euler[maxN << 1], Esiz, rid[maxN]; ll dis[maxN];
    void dfs(int u, int fa)
    {
        deep[u] = deep[fa] + 1; rid[u] = Esiz + 1;
        for(int i=head[u], v; ~i; i=edge[i].nex)
        {
            v = edge[i].to;
            if(v == fa) continue;
            dis[v] = dis[u] + edge[i].val;
            euler[++Esiz] = u;
            dfs(v, u);
        }
        euler[++Esiz] = u;
    }
    int mn[maxN << 1][22];
    inline void RMQ_Init()
    {
        for(int i=1; i<=Esiz; i++) mn[i][0] = euler[i];
        for(int j=1; (1 << j) <= Esiz; j++)
        {
            for(int i=1; i + (1 << j) - 1 <= Esiz; i++)
            {
                if(deep[mn[i][j - 1]] < deep[mn[i + (1 << (j - 1))][j - 1]]) mn[i][j] = mn[i][j - 1];
                else mn[i][j] = mn[i + (1 << (j - 1))][j - 1];
            }
        }
    }
    inline int Rmq(int l, int r)
    {
        int det = r - l + 1, kk = LOG_2[det];
        if(deep[mn[l][kk]] <= deep[mn[r - (1 << kk) + 1][kk]]) return mn[l][kk];
        else return mn[r - (1 << kk) + 1][kk];
    }
    inline int _LCA(int u, int v)
    {
        int l = rid[u], r = rid[v];
        if(l > r) swap(l, r);
        return Rmq(l, r);
    }
    inline ll _Dis(int u, int v)
    {
        int lca = _LCA(u, v);
        return dis[u] + dis[v] - 2 * dis[lca];
    }
} A_lca;
inline bool cmp(int e1, int e2) { return A_lca.deep[e1] > A_lca.deep[e2]; }
pair<int, int> t[maxN];
pair<int, int> solve(pair<int, int> A, pair<int, int> B)
{
    if(!A.first || !B.first) return MP(0, 0);
    pair<int, int> ans;
    int s[4];
    s[0] = A_lca._LCA(A.first, B.first); s[1] = A_lca._LCA(A.first, B.second);
    s[2] = A_lca._LCA(A.second, B.first); s[3] = A_lca._LCA(A.second, B.second);
    sort(s, s + 4, cmp);
    if(s[0] ^ s[1])
    {
        ans = MP(s[0], s[1]);
    }
    else ans = MP(0, 0);
    return ans;
}
struct BIT_Tree
{
    pair<int, int> tree[maxN << 2];
    void build(int rt, int l, int r)
    {
        if(l == r) { tree[rt] = t[l]; return; }
        int mid = HalF;
        build(Lson); build(Rson);
        tree[rt] = solve(tree[lsn], tree[rsn]);
    }
    pair<int, int> query(int rt, int l, int r, int ql, int qr)
    {
        if(ql <= l && qr >= r) return tree[rt];
        int mid = HalF;
        if(qr <= mid) return query(QL);
        else if(ql > mid) return query(QR);
        else return solve(query(QL), query(QR));
    }
} bt;
inline void init()
{
    cnt = 0;
    for(int i=1; i<=N; i++) head[i] = -1;
    for(int i = 1, j = 2, k = 0; i<=(N << 1); i++)
    {
        if(i == j) { j <<= 1; k ++; }
        LOG_2[i] = k;
    }
}
int main()
{
    scanf("%d", &N);
    init();
    for(int i=1, u, v, w; i<N; i++)
    {
        scanf("%d%d%d", &u, &v, &w);
        _add(u, v, w);
    }
    A_lca.dfs(1, 0);
    A_lca.RMQ_Init();
    scanf("%d", &M);
    for(int i=1; i<=M; i++) scanf("%d%d", &t[i].first, &t[i].second);
    bt.build(1, 1, M);
    scanf("%d", &Q);
    int L, R; pair<int, int> now;
    while(Q--)
    {
        scanf("%d%d", &L, &R);
        now = bt.query(1, 1, M, L, R);
        if(now.first) printf("%lld\n", A_lca._Dis(now.first, now.second));
        else printf("0\n");
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
LCA+路径压缩的方式可以用于求解树上的桥,具体实现步骤如下: 1. 对于树上每个节点,记录其在树中的深度(或者高度)以及其父亲节点。 2. 对于每个节点,记录其在树上的最小深度(或最小高度)以及其所在子树中深度最小的节点。 3. 对于每条边(u, v),设u的深度小于v的深度(或者高度),则如果v的子树中没有深度小于u的节点,则(u, v)是桥。 具体的实现过程如下: 首先,我们需要对树进行预处理,求出每个节点的深度以及其父亲节点。可以使用深度优先搜索(DFS)或广度优先搜索(BFS)来实现。在这里我们使用DFS来实现: ```c++ vector<int> adj[MAX_N]; // 树的邻接表 int n; // 树的节点数 int dep[MAX_N], fa[MAX_N]; // dep[i]表示节点i的深度,fa[i]表示节点i的父亲节点 void dfs(int u, int f, int d) { dep[u] = d; fa[u] = f; for (int v : adj[u]) { if (v != f) { dfs(v, u, d + 1); } } } ``` 接下来,我们需要计算每个节点所在子树中深度最小的节点。我们可以使用LCA(最近公共祖先)的方法来实现。具体来说,我们可以使用倍增算法来预处理出每个节点的2^k级祖先,并且在查询LCA时使用路径压缩的方式优化时间复杂度。这里我们不展开讲解LCA和倍增算法的细节,如果你对此感兴趣,可以参考其他资料进行学习。 ```c++ const int MAX_LOG_N = 20; // log2(n)的上取整 int anc[MAX_N][MAX_LOG_N]; // anc[i][j]表示节点i的2^j级祖先 int mn[MAX_N]; // mn[i]表示节点i所在子树中深度最小的节点 void precompute() { // 预处理anc数组 for (int j = 1; j < MAX_LOG_N; j++) { for (int i = 1; i <= n; i++) { if (anc[i][j - 1] != -1) { anc[i][j] = anc[anc[i][j - 1]][j - 1]; } } } // 计算mn数组 for (int i = 1; i <= n; i++) { mn[i] = i; for (int j = 0; (1 << j) <= dep[i]; j++) { if ((dep[i] & (1 << j)) != 0) { mn[i] = min(mn[i], mn[anc[i][j]]); i = anc[i][j]; } } } } ``` 最后,我们可以使用LCA+路径压缩的方式来判断每条边是否为桥。具体来说,对于每条边(u, v),我们需要判断v的子树中是否存在深度小于u的节点。如果存在,则(u, v)不是桥,否则(u, v)是桥。 ```c++ bool is_bridge(int u, int v) { if (dep[u] > dep[v]) swap(u, v); if (mn[v] != u) return true; // 子树中存在深度小于u的节点 return false; // 子树中不存在深度小于u的节点 } ``` 完整代码如下:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuliwuliii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值