题目描述 Description
小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上。有一天,他们想爬到一个节点上去搞基,但是作为两只虫子,他们不想花费太多精力。已知从某个节点爬到其父亲节点要花费 c 的能量(从父亲节点爬到此节点也相同),他们想找出一条花费精力最短的路,以使得搞基的时候精力旺盛,他们找到你要你设计一个程序来找到这条路,要求你告诉他们最少需要花费多少精力
输入描述 Input Description
第一行一个n,接下来n-1行每一行有三个整数u,v, c 。表示节点 u 爬到节点 v 需要花费 c 的精力。
第n+1行有一个整数m表示有m次询问。接下来m行每一行有两个整数 u ,v 表示两只虫子所在的节点
输出描述 Output Description
一共有m行,每一行一个整数,表示对于该次询问所得出的最短距离。
样例输入 Sample Input
3
1 0 1
2 0 1
3
1 0
2 0
1 2
样例输出 Sample Output
1
1
2
数据范围及提示 Data Size & Hint
1<=n<=50000, 1<=m<=75000, 0<=c<=1000
这是一题LCA基础题、模板题,我认为很适合LCA入门,反正我自己研究了好久。。。不多说先上第一份AC代码,精华照旧在注释里。后面感觉会有更好的代码,所以日后可能更新。
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <vector>
#include <math.h>
#include <iostream>
#include <string>
#include <string.h>
const int MAX=1e5+5;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
struct node
{
int son, cost, next, lca;
};
struct Node /// 这里只是把链式前向星的初始化和增边操作封装到结构体Node里了
{
int flag, first[MAX];
node edge[2*MAX];/// 因为我们后面建一条边的同时也要建一条反向边,所以edge[]
///大小要翻倍
void init()/// 初始化
{
flag = 0;
memset(first, -1, sizeof(first));
memset(edge, 0, sizeof(edge));
}
void add(int a, int b, int c)/// 加一条a到b、权为c的边
{
edge[flag].next = first[a];
edge[flag].son = b;
edge[flag].cost = c;
first[a] = flag++;
}
}edge, query;/// edge用来建输入的图,query用来存所有的询问,离线处理完,一次
///性返回所有询问的答案
int dist[MAX];/// 存各个点到根结点的路程
int dad[MAX], ans[2*MAX];
int find_back(int son)
{
return dad[son]==son? son: dad[son]=find_back(dad[son]);/// 找爹,顺便压缩路
///径(从大佬博客上学的,很简洁)
}
bool vis[MAX];
void LCA(int root)
{
int i, a;
vis[root] = true;/// 既然建了双向边,走过的点我们标记一下,避免又跑回来
for (i=edge.first[root]; i!=-1; i=edge.edge[i].next)/// 遍历当前结点所有儿子
{
if (!vis[edge.edge[i].son])/// 该点没访问过的话
{
dist[edge.edge[i].son] = dist[root] + edge.edge[i].cost;/// 更新一下到根结
///点的路程
LCA(edge.edge[i].son);/// 继续递归
dad[edge.edge[i].son] = root;/// 更新一下父结点
}
}
for (i=query.first[root]; i!=-1; i=query.edge[i].next)/// 当前结点被询问过的
///话,它的所有儿子也都是被询问的点,看清楚这里是query
if (vis[query.edge[i].son])/// 另一个被询问的点也被访问过的话,说明信息也
{ ///已经,那我们开始生成答案
query.edge[i].lca = find_back(query.edge[i].son);/// 这时候往回找祖先便能
///得到最近公共祖先。为什么呢?我们要明确,这一整个循环是在回溯过程中
///完成的,也就是说,结点i上面的结点还没有更新父亲。这种情况下如果两个
///被询问的点都被访问过了,一定是另一个被询问的点先被访问过,那么我们
///直接找query.edge[i].son这个结点往上所能找到的祖先,便是最近公共祖先
a=dist[root]+dist[query.edge[i].son]-2*dist[query.edge[i].lca];/// 这里画
///一下图很好理解,碰面需要的精力,即从各自点到根结点需要的精力,减去
///重复算了两次的从最近公共祖先到根结点所需的精力
ans[i] = a; /// 答案记下来,等等离线是要按顺序输出的
if (i%2) /// 因为我们存询问的时候也是正反一起放进去,所以每一对
ans[i-1] = a; ///“偶 奇”下标其实是同一次询问,答案一个样
else
ans[i+1] = a;
}
}
int main()
{
int n, m, i, j, u, v, c;
cin >> n;
edge.init(), query.init();/// 记得先初始化
for (i=1; i<n; i++)
scanf("%d%d%d", &u, &v, &c), edge.add(v, u, c), edge.add(u, v, c);
/// 因为建树,哪个点作为根结点都是一样的,所以正反边一起建,方便我们任性
cin >> m;
for (i=1; i<=m; i++)
scanf("%d%d", &u, &v), query.add(v, u, i), query.add(u, v, i);
/// 询问也是正反一起放进去,因为你不知道哪个点会在dfs时被先被标记
for (i=1; i<n; i++)
dad[i] = i;/// 所有节点的父亲初始化为自己
LCA(0);/// 我偏要0作为根结点,扔谁进去谁是大祖宗
for (i=0; i<m; i++)
printf("%d\n", ans[i*2+1]);/// 根据我们存询问的方式可以知道,每一对“偶 奇”
///下标其实是同一次询问,所以我选择都输出“奇”位置的那个。
return 0;
}
附赠一组案例,不懂再琢磨琢磨
Input
10
0 1 570
0 2 632
0 3 229
1 4 998
4 5 565
2 6 956
3 7 21
4 8 127
0 9 727
10
9 1
4 7
8 2
4 3
5 1
0 4
0 3
7 6
2 3
4 6
Output
1297
1818
2327
1797
1563
1568
229
1838
861
3156