How far away?
C - How far away ?
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
随机推荐
- bzoj 4199: [Noi2015]品酒大会【后缀数组+单调栈+并查集】
用SA求出height数组,然后发现每个height值都有一个贡献区间(因为点对之间要依次取min) 用单调栈处理出区间,第一问就做完了 然后用并查集维护每个点的贡献(?),从大到小枚举height, ...
- bzoj 1076: [SCOI2008]奖励关【状压dp+概率dp】
设f[i][s]为前i步,选的礼物集合为s的方案数,然而并不会转移-- 看了hzwer的blog,发现要倒着转移,然后答案就是f[1][0] 妙啊 #include<iostream> # ...
- LuoguP1370 Charlie的云笔记序列 【dp】By cellur925
题目传送门 题目大意:给你一个序列,求出它所有区间的本质不同的子序列个数.(空序列也算作本质不同),数据范围$1e5$. 我们肯定是不能一个个枚举区间的...而且这个复杂度下,也就大概$O(n)$或$ ...
- noi,ac第五场部分题解 By cellur925
题目质量还是不错的,只是我太菜了== 传送门 T1:序列计数(count) 题目描述 长度为n+1的序列A,其中的每个数都是不大于n的正整数,且n以内每个正整数至少出现一次. 对于每一个正整数k=1, ...
- Centos 7.x 配置Gitlab
GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务. 1. 安装并配置必要的依赖关系 如果你想使用 Postfix 发送邮件,请在安装过程中根 ...
- Reference for shell scripting
${var} 和 $var的区别 http://stackoverflow.com/questions/8748831/when-do-we-need-curly-braces-in-variable ...
- 利用lsof去查看Unix/Linux进程打开了哪些文件
利用lsof去查看Unix/Linux进程打开了哪些文件 今天用了一下lsof,发现这个linux的小工具,功能非常强大而且好用. 我们可以方便的用它查看应用程序进程打开了哪些文件或者对于特定的一个文 ...
- qconbeijing2016
http://2016.qconbeijing.com/schedule 大会日程 2016年04月21日 星期四 09:15 开场致辞 地点 1号厅 主题演讲 工程效率提升 业务核心架构 容器集 ...
- 【学习笔记】响应式布局的常用解决方案(媒体查询、百分比、rem、和vw/vh)
原文转载:https://blog.csdn.net/sinat_17775997/article/details/81020417 一.媒体查询 不同物理分辨率的设备,在还原设计稿时,css中设置的 ...
- match,location,history
哇,平常写路由时基本就是简单的按照组件给的示例写,从来没有考虑为什么,又遇见了路由相关的问题,先记录一下问题,好好捋一下,哎,好香要个大佬来带带我呀,每次遇到问题要解决好久 问题: 判断是否登录之后跳 ...