How far away?

C - How far away ?

 HDU - 2586 

There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this "How far is it if I want to go from house A to house B"? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path("simple" means you can't visit a place twice) between every two houses. Yout task is to answer all these curious people.

Input

First line is a single integer T(T<=10), indicating the number of test cases. 
  For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0<k<=40000).The houses are labeled from 1 to n. 
  Next m lines each has distinct integers i and j, you areato answer the distance between house i and house j.

Output

For each test case,output m lines. Each line represents the answer of the query. Output a bland line after each test case.

Sample Input

2
3 2
1 2 10
3 1 15
1 2
2 3

2 2
1 2 100
1 2
2 1

Sample Output

10
25
100
100
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

const int N = 40010;
const int M = 25;
int dp[2 * N][M];
bool vis[N];
struct edge
{
    int v, w, next;
} e[2 * N];
int tot, head[N];
int tol;
inline void add(int u,int v,int w)
{
    e[tol].v = v;
    e[tol].w = w;
    e[tol].next = head[u];
    head[u] = tol++;
    u = u ^ v;
    v = v ^ u;
    u = v ^ u;
    e[tol].v = v;
    e[tol].w = w;
    e[tol].next = head[u];
    head[u] = tol++;
}
int ver[2 * N], R[2 * N], first[N], dir[N];
void dfs(int u, int dep)
{
    vis[u] = 1;
    ver[++tot] = u;
    first[u] = tot;
    R[tot] = dep;
    for (int k = head[u]; k != -1; k = e[k].next)
    {
        if (!vis[e[k].v]) {
            int v = e[k].v, w = e[k].w;
            dir[v] = dir[u] + w;
            dfs(v, dep + 1);
            ver[++tot] = u;
            R[tot] = dep;
        }
    }
}
void ST(int n)
{
    for (int i = 1; i <= n;i++)
        dp[i][0] = i;
    for (int j = 1; (1 << j) <= n;j++) {
        for (int i = 1; i + (1 << j) - 1 <= n;i++) {
            int a = dp[i][j - 1];
            int b = dp[i + (1 << (j - 1))][j-1];
            dp[i][j] = R[a] < R[b] ? a : b;
        }
    }
}
int RMQ(int l,int r)
{
    int k = 0;
    while ((1<<(k+1))<=r-l+1)
        k++;
    int a=dp[l][k],b=dp[r-(1<<k)+1][k];
    return R[a] < R[b] ? a : b;
}
int LCA(int u,int v)
{
    int x = first[u], y = first[v];
    if (x>y)
        swap(x, y);
    int res = RMQ(x, y);
    return ver[res];
}
int main()
{
    int cas;
    scanf("%d", &cas);
    while (cas--)
    {
        int n, q;
        tol = 0;
        scanf("%d%d", &n, &q);
        memset(head, -1, sizeof(head));
        memset(vis, 0, sizeof(vis));
        for (int i = 1; i < n;i++) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            add(u, v, w);
        }
        tot = 0;
        dir[1] = 0;
        dfs(1, 1);
        ST(2 * n - 1);
        while (q--) {
            int u, v;
            scanf("%d%d", &u, &v);
            int lca = LCA(u, v);
            printf("%d\n", dir[u] + dir[v] - 2 * dir[lca]);
        }
    }
    getchar();
    getchar();
    return 0;
}

这是一道最近公共祖先的问题。

其实主要还是对RMQ-ST算法的理解,我的那篇  最近公共祖先-三  博客写的不是很清楚,这里就写明白一点。

 

首先说一下链式向前星,就是代码里面的双向建图。head数组里面存的是每一个起点的最大边号,然后next数组里面存的是该起点之前边的边号。

也就是说,假设点 i ,连接着 1 3 5 边号的边,head数组里面存的就是 5 ,然后next数组里面存的就是边5的前一条边,比如4号边,4和5是一条直接联通的边,然后还顺次连结着其它的边。

这样的话,在dfs搜索的时候就可以通过for循环,将某条联通的边给遍历完。因为 i 是在不断被赋值给next[ i ]的,然后next[ i ]里面存的就是前一条边的边号,以此类推就可以让for循环遍历完整个路径。

对于向下搜索时,起到引导作用的是,该点指向的点,然后跳转到目标点,进行目标点的深搜,dfs是一个横向加纵向的过程。

然后就是dfs的回溯了,说的很高大上,其实不然,回溯也就是在走一遍之前走过的点。因为当目前的路走不通时,也就是for循环执行完毕,内层的dfs执行也就结束了,然后走到外层,该执行dfs的下一句话了,执行的这句话就是回溯。

 

接着说RMQ-ST,它实际上是通过深搜得到遍历时走过点的顺序加编号实现的。

设每一个点都有它独立的编号,然后第几步走过该点成为序号。

dfs深搜的时候要建立一个序号数组,简称X,建立一个深度数组,简称S,建立一个第一次访问数组,简称F。

它们里面存的是,序号数组:序号作为下标,编号作为内容。

深度数组:序号作为下标,深度作为内容。

第一次访问数组:编号作为下标,第一次出现的序号作为内容。

然后这就简单了,当输入两个点的时候,通过F数组找到它们的第一次序号,然后两个数从小到大排列,分别作为左端点和右端点。利用已经存下的dp数组,查找对应区间的最小深度对应的序号,用S数组比较两个序号的大小,返回深度较小的序号。再通过序号,利用F数组查找是哪个点第一次出现在该序号上,这就找到了。

当然,这说的只是大致过程,现在说所dp数组。dp数组是是怎么形成的呢?

大致是这样的,区间长度为一的数组dp[ i ][ 0 ]应存入该序号,易证。

我们要得到的是该区间深度最小值对应的序号,然后再比较的dp[ i ][ 1 ] ,2的一次方是2,然后区间长度就是2,然后就把之前的区间长度为一的dp[ i ][ 0 ]拿来比较,比较该序号的深度最小值,因为有S数组,有序号对应深度的关系。

以上就是全过程了,细节的实现,放几个传送门吧。

链式向前星:https://blog.csdn.net/qq_40046426/article/details/81906436

RMQ-ST带图:https://blog.csdn.net/gesanghuazgy/article/details/51498213

ST算法:https://blog.csdn.net/qq_41090676/article/details/82713912

转载于:https://www.cnblogs.com/xyqxyq/p/10211381.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`pd.merge()` 是 Pandas 库中用于合并数据的函数。它可以根据一个或多个键将不同的 DataFrame 对象合并在一起。 函数语法如下: ```python pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=False, suffixes=('_x', '_y'), copy=True, indicator=False, validate=None) ``` 参数说明: - `left`:要合并的左侧 DataFrame。 - `right`:要合并的右侧 DataFrame。 - `how`:合并方式,可选值包括 `inner`(取交集)、`outer`(取并集)、`left`(以左侧 DataFrame 为准)、`right`(以右侧 DataFrame 为准)。 - `on`:要合并的列名,必须同时存在于左右两个 DataFrame 中。 - `left_on`:左侧 DataFrame 中用作合并键的列名。 - `right_on`:右侧 DataFrame 中用作合并键的列名。 - `left_index`:是否使用左侧 DataFrame 的索引作为合并键。 - `right_index`:是否使用右侧 DataFrame 的索引作为合并键。 - `sort`:是否按照合并键进行排序。 - `suffixes`:如果左右 DataFrame 存在相同的列名,用于指定列名后缀的元组。 - `copy`:是否在合并时复制数据。 - `indicator`:是否添加 `_merge` 列,用于指示合并方式。 - `validate`:是否检查合并键是否有效。 示例代码: ```python import pandas as pd # 创建两个 DataFrame df1 = pd.DataFrame({ 'key': ['A', 'B', 'C', 'D'], 'value': [1, 2, 3, 4] }) df2 = pd.DataFrame({ 'key': ['B', 'D', 'E', 'F'], 'value': [5, 6, 7, 8] }) # 根据 key 列进行内连接 df3 = pd.merge(df1, df2, on='key', how='inner') print(df3) ``` 输出结果: ``` key value_x value_y 0 B 2 5 1 D 4 6 ``` 在这个例子中,我们首先创建了两个 DataFrame,然后使用 `pd.merge()` 函数将它们根据 `key` 列进行内连接。由于 `key` 列中只有 `'B'` 和 `'D'` 这两个值同时存在于两个 DataFrame 中,因此最终合并的结果只包含这两行数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值