[FJOI 2014]最短路径树问题

Description

给一个包含n个点,m条边的无向连通图。从顶点1出发,往其余所有点分别走一次并返回。
往某一个点走时,选择总长度最短的路径走。若有多条长度最短的路径,则选择经过的顶点序列字典序最小的那条路径(如路径A为1,32,11,路径B为1,3,2,11,路径B字典序较小。注意是序列的字典序的最小,而非路径中节点编号相连的字符串字典序最小)。到达该点后按原路返回,然后往其他点走,直到所有点都走过。
可以知道,经过的边会构成一棵最短路径树。请问,在这棵最短路径树上,最长的包含K个点的简单路径长度为多长?长度为该最长长度的不同路径有多少条?
这里的简单路径是指:对于一个点最多只经过一次的路径。不同路径是指路径两端端点至少有一个不同,点A到点B的路径和点B到点A视为同一条路径。

Input

第一行输入三个正整数n,m,K,表示有n个点m条边,要求的路径需要经过K个点。接下来输入m行,每行三个正整数Ai,Bi,Ci(1<=Ai,Bi<=n,1<=Ci<=10000),表示Ai和Bi间有一条长度为Ci的边。数据保证输入的是连通的无向图。

Output

输出一行两个整数,以一个空格隔开,第一个整数表示包含K个点的路径最长为多长,第二个整数表示这样的不同的最长路径有多少条。

Sample Input

6 6 4
1 2 1
2 3 1
3 4 1
2 5 1
3 6 1
5 6 1

Sample Output

3 4

HINT

对于所有数据n<=30000,m<=60000,2<=K<=n。
数据保证最短路径树上至少存在一条长度为K的路径。

题解

看错题意,以为是求包含 $k$ 个点的简单路径共多少条。调了好久...

首先求字典序最小的最短路树,考虑将边拆成两条单向边,然后按终点从大到小排序,按序插入链式前向星中,保证找到的第一条最短路就是字典序最小的。

点分就比较裸了,记深度为 $i$ 时最大的路径长度为 $sum_i$ ,长度为 $sum_i$ ,且深度为 $i$ 的路径数为 $cnt_i$ 直接转移就好了。

  1 //It is made by Awson on 2018.1.4
  2 #include <set>
  3 #include <map>
  4 #include <cmath>
  5 #include <ctime>
  6 #include <queue>
  7 #include <stack>
  8 #include <cstdio>
  9 #include <string>
 10 #include <vector>
 11 #include <cstdlib>
 12 #include <cstring>
 13 #include <iostream>
 14 #include <algorithm>
 15 #define LL long long
 16 #define LD long double
 17 #define Max(a, b) ((a) > (b) ? (a) : (b))
 18 #define Min(a, b) ((a) < (b) ? (a) : (b))
 19 using namespace std;
 20 const int N = 30000;
 21 const int M = 60000;
 22 const int INF = ~0u>>1;
 23  
 24 int n, m, k;
 25 struct tt {
 26     int to, next, cost;
 27 }edge[(N<<1)+5];
 28 int path[N+5], top, ans1, ans2;
 29 void add(int u, int v, int c) {
 30     edge[++top].to = v;
 31     edge[top].next = path[u];
 32     edge[top].cost = c;
 33     path[u] = top;
 34 }
 35  
 36 namespace SPFA {
 37     int a, b, c;
 38     struct ss {
 39     int u, v, c;
 40     ss() {}
 41     ss(int _u, int _v, int _c) {
 42         u = _u, v = _v, c = _c;
 43     }
 44     bool operator < (const ss &b) const {
 45         return v > b.v;
 46     }
 47     }w[(M<<1)+5];
 48     struct sss {
 49     int to, next, cost;
 50     }edge[(M<<1)+5];
 51     int path[N+5], top, tot, pre[N+5], vis[N+5], prec[N+5];
 52     LL dist[N+5];
 53     void add_e(int u, int v, int c) {
 54     edge[++top].to = v;
 55     edge[top].cost = c;
 56     edge[top].next = path[u];
 57     path[u] = top;
 58     }
 59     void spfa() {
 60     memset(dist, 127/3, sizeof(dist)); dist[1] = 0;
 61     queue<int>Q; Q.push(1); vis[1] = 1;
 62     while (!Q.empty()) {
 63         int u = Q.front(); Q.pop(); vis[u] = 0;
 64         for (int i = path[u]; i; i = edge[i].next)
 65         if (dist[edge[i].to] > dist[u]+edge[i].cost) {
 66             dist[edge[i].to] = dist[u]+edge[i].cost;
 67             pre[edge[i].to] = u, prec[edge[i].to] = edge[i].cost;
 68             if (!vis[edge[i].to]) {
 69             vis[edge[i].to] = 1; Q.push(edge[i].to);
 70             }
 71         }
 72     }
 73     }
 74     void main() {
 75     scanf("%d%d%d", &n, &m, &k);
 76     for (int i = 1; i <= m; i++) {
 77         scanf("%d%d%d", &a, &b, &c);
 78         w[++tot] = ss(a, b, c), w[++tot] = ss(b, a, c);
 79     }
 80     sort(w+1, w+1+tot);
 81     for (int i = 1; i <= tot; i++) add_e(w[i].u, w[i].v, w[i].c);
 82     spfa();
 83     for (int i = 2; i <= n; i++) add(pre[i], i, prec[i]), add(i, pre[i], prec[i]);
 84     }
 85 }
 86 namespace Point_divide {
 87     int size[N+5], mx[N+5], minsize, root, vis[N+5], cnt[N+5], sum[N+5];
 88     void get_size(int o, int fa) {
 89     size[o] = 1, mx[o] = 0;
 90     for (int i = path[o]; i; i = edge[i].next)
 91         if (edge[i].to != fa && !vis[edge[i].to]) {
 92         get_size(edge[i].to, o);
 93         size[o] += size[edge[i].to];
 94         mx[o] = Max(mx[o], size[edge[i].to]);
 95         }
 96     }
 97     void get_root(int o, int rt, int fa) {
 98     mx[o] = Max(mx[o], size[rt]-size[o]);
 99     if (mx[o] < minsize) minsize = mx[o], root = o;
100     for (int i = path[o]; i; i = edge[i].next)
101         if (edge[i].to != fa && !vis[edge[i].to]) get_root(edge[i].to, rt, o);
102     }
103     void get_ans(int o, int fa, int dep, int cost) {
104     if (dep >= k) return;
105     if (cnt[k-1-dep] && ans1 < cost+sum[k-1-dep]) ans1 = cost+sum[k-1-dep], ans2 = cnt[k-1-dep];
106     else if (cnt[k-1-dep] && ans1 == cost+sum[k-1-dep]) ans2 += cnt[k-1-dep];
107     for (int i = path[o]; i; i = edge[i].next)
108         if (edge[i].to != fa && !vis[edge[i].to]) get_ans(edge[i].to, o, dep+1, cost+edge[i].cost);
109     }
110     void get_update(int o, int fa, int dep, int cost) {
111     if (dep >= k) return;
112     if (sum[dep] < cost) sum[dep] = cost, cnt[dep] = 1;
113     else if (sum[dep] == cost) ++cnt[dep];
114     for (int i = path[o]; i; i = edge[i].next)
115         if (edge[i].to != fa && !vis[edge[i].to]) get_update(edge[i].to, o, dep+1, cost+edge[i].cost);
116     }
117     void get_clean(int o, int fa, int dep) {
118     if (dep >= k) return;
119     cnt[dep] = 0, sum[dep] = 0;
120     for (int i = path[o]; i; i = edge[i].next)
121         if (edge[i].to != fa && !vis[edge[i].to]) get_clean(edge[i].to, o, dep+1);
122     }
123     void work(int o) {
124     minsize = INF;
125     get_size(o, 0), get_root(o, o, 0);
126     vis[root] = 1; cnt[0] = 1;
127     for (int i = path[root]; i; i = edge[i].next)
128         if (!vis[edge[i].to]) get_ans(edge[i].to, root, 1, edge[i].cost), get_update(edge[i].to, root, 1, edge[i].cost);
129     cnt[0] = 0;
130     for (int i = path[root]; i; i = edge[i].next)
131         if (!vis[edge[i].to]) get_clean(edge[i].to, root, 1);
132     for (int i = path[root]; i; i = edge[i].next)
133         if (!vis[edge[i].to]) work(edge[i].to);
134     }
135     void main() {work(1); }
136 }
137 void work() {
138     SPFA::main();
139     Point_divide::main();
140     printf("%d %d\n", ans1, ans2);
141 }
142 int main() {
143     work();
144     return 0;
145 }

 

转载于:https://www.cnblogs.com/NaVi-Awson/p/8202372.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值