CODE[VS] 2370 (fjutacm 3780) 小机房的树 LCA最小公共祖先 dfs

题目描述 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

发布了19 篇原创文章 · 获赞 0 · 访问量 497

猜你喜欢

转载自blog.csdn.net/qq_43317133/article/details/100045715