题解 - 树上游走(二)(上海月赛2024.7甲组T1)

嚴華  论坛元老 | 2024-12-24 21:10:27 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1047|帖子 1047|积分 3141

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
题目形貌

给定一个棵由编号为 1~n 这 n 个节点构成的树,及 m 个关键节点,树的第 i 条双向边连接 ui 和 vi ,长度为 wi 。你可以从树上任何一个节点出发开始游走,到任何一个节点结束,但整个游走过程中必须经过给定的 m 个关键节点。
叨教,应当如何计划路线,能够使得整个游走过程中,经过的路径长度最短。
输入格式

输入第一行,两个正整数 n,m
接下来 n−1 行,每行 3 个正整数,其中第 i+1 行分别表现第 i 条边的 ui,vi,wi。
输入最后行,m 个正整数,分别表现 m 个关键节点的编号 ci。
输出格式

输出共一行,表现所求最短长度。
数据范围

1≤m≤n≤1e5,1​ ≤ ui,vi,ci ≤n, 1≤w ≤1e4
样例数据

输入:

5 3
1 2 2
2 3 4
2 4 5
1 5 1
3 4 5
输出:

15
阐明:

3–>2–>1–>5–>1–>2–>4
4 2 1 1 2 5
分析

树形dp + 换根

  • 首先考虑对于出发点固定,应该怎么做。
假设出发点为st,假设m个关键节点分布在st的k个子树内,则我们将只经过其中1个子树一次,然后别的k-1个子树的每条边经过两次。
一个很显然的贪婪是,为了使总旅程最短,我们应该满足 只经过1次的子树的旅程和 最大。
可以dfs来求
其中f代表从u节点访问u子树的所有关键点然后再返回u节点需要的总旅程(即所有路径均为两倍),
mx[0]和mx[1]分别表现i的子树的最大值和次大值。(至于为什么还需要维护次大值,后续会讲到)
  1. void dfs(int u,int fa){
  2.     if(flag[u]) cnt[u] = 1;
  3.     for(int i = h[u];i != -1;i = ne[i]){
  4.         int j = e[i],val = w[i];
  5.         if(j != fa){
  6.             dfs(j,u);
  7.             
  8.             if(cnt[j]){
  9.                 cnt[u] += cnt[j];
  10.                 f[u] += f[j] + 2 * val;
  11.                 int tmp = mx[j][0] + val;
  12.                 if(tmp > mx[u][0]){
  13.                     mx[u][1] = mx[u][0],id[u][1] = id[u][0];
  14.                     mx[u][0] = tmp,id[u][0] = j;
  15.                 }else if(tmp > mx[u][1]){
  16.                     mx[u][1] = tmp,id[u][1] = j;
  17.                 }
  18.             }
  19.         }
  20.     }
  21. }
复制代码
终极效果即为 f[st] - mx[st][0]

  • 然后考虑出发点不固定
可以采用换根dp来写
对于                                    u                         →                         j                              u \rightarrow j                  u→j 这条边,根从                                    u                              u                  u 转移到                                    j                              j                  j ,
若 j 子树内关键节点数为0,则 f[j] = f + 2 * val;
若 j 子树内关键节点数为m,则 f[j] 不变;
否则, f[j] = f
若 j 子树内关键节点数为m,则还需要更新mx[j][0]和mx[j][1]的值。(具体细节见代码)
  1. void dp(int u,int fa){
  2.     res = min(res,f[u] - mx[u][0]);
  3.     for(int i = h[u];i != -1;i = ne[i]){
  4.         int j = e[i],val = w[i];
  5.         if(j != fa){
  6.             if(!cnt[j]) f[j] = f[u] + 2 * val;
  7.             else if(cnt[j] == m) f[j] = f[j];
  8.             else f[j] = f[u];
  9.             if(cnt[j] != m){
  10.                 int tmp;
  11.                 if(id[u][0] == j) tmp = mx[u][1] + val;
  12.                 else tmp = mx[u][0] + val;
  13.                 if(tmp > mx[j][0]){
  14.                     mx[j][1] = mx[j][0],id[j][1] = id[j][0];
  15.                     mx[j][0] = tmp,id[j][0] = u;
  16.                 }else if(tmp > mx[j][1]){
  17.                     mx[j][1] = tmp,id[j][1] = u;
  18.                 }
  19.            }
  20.             dp(j,u);
  21.         }
  22.     }
  23. }
复制代码
完备代码

  1. #include<bits/stdc++.h>using namespace std;typedef long long LL;const int N = 1e5 + 10;int n,m;bool flag[N];int h[N],e[N * 2],w[N * 2],ne[N * 2],idx;LL cnt[N],f[N],mx[N][2],id[N][2],res;void add(int a,int b,int c){    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;}void dfs(int u,int fa){
  2.     if(flag[u]) cnt[u] = 1;
  3.     for(int i = h[u];i != -1;i = ne[i]){
  4.         int j = e[i],val = w[i];
  5.         if(j != fa){
  6.             dfs(j,u);
  7.             
  8.             if(cnt[j]){
  9.                 cnt[u] += cnt[j];
  10.                 f[u] += f[j] + 2 * val;
  11.                 int tmp = mx[j][0] + val;
  12.                 if(tmp > mx[u][0]){
  13.                     mx[u][1] = mx[u][0],id[u][1] = id[u][0];
  14.                     mx[u][0] = tmp,id[u][0] = j;
  15.                 }else if(tmp > mx[u][1]){
  16.                     mx[u][1] = tmp,id[u][1] = j;
  17.                 }
  18.             }
  19.         }
  20.     }
  21. }
  22. void dp(int u,int fa){
  23.     res = min(res,f[u] - mx[u][0]);
  24.     for(int i = h[u];i != -1;i = ne[i]){
  25.         int j = e[i],val = w[i];
  26.         if(j != fa){
  27.             if(!cnt[j]) f[j] = f[u] + 2 * val;
  28.             else if(cnt[j] == m) f[j] = f[j];
  29.             else f[j] = f[u];
  30.             if(cnt[j] != m){
  31.                 int tmp;
  32.                 if(id[u][0] == j) tmp = mx[u][1] + val;
  33.                 else tmp = mx[u][0] + val;
  34.                 if(tmp > mx[j][0]){
  35.                     mx[j][1] = mx[j][0],id[j][1] = id[j][0];
  36.                     mx[j][0] = tmp,id[j][0] = u;
  37.                 }else if(tmp > mx[j][1]){
  38.                     mx[j][1] = tmp,id[j][1] = u;
  39.                 }
  40.            }
  41.             dp(j,u);
  42.         }
  43.     }
  44. }
  45. int main(){    ios::sync_with_stdio(false);    cin.tie(0),cout.tie(0);    memset(h,-1,sizeof h);    cin >> n >> m;    for(int i = 0,u,v,w;i < n - 1;i++){        cin >> u >> v >> w;        add(u,v,w),add(v,u,w);    }    for(int i = 1,j;i <= m;i++){        cin >> j;        flag[j] = true;    }    dfs(1,-1);    res = f[1] - mx[1][0];    dp(1,-1);    cout << res;    return 0;}
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

嚴華

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表