P4716 【模板】最小树形图
输入输出样例
输入 #1
4 6 1
1 2 3
1 3 1
4 1 2
4 2 2
3 2 1
3 4 1
输出 #1
3
输入 #2
4 6 3
1 2 3
1 3 1
4 1 2
4 2 2
3 2 1
3 4 1
输出 #2
4
输入 #3
4 6 2
1 2 3
1 3 1
4 1 2
4 2 2
3 2 1
3 4 1
输出 #3
1
总结
头一次了解到朱刘算法(大佬写的详解,强烈建议初学者观看,很详细)
半伪代码
while(true){
步骤一:找到除了根节点外每个点被指向的最短距离的线,连接起来。
步骤二未有其他点指向,有则继续下方步骤,无则跳出返回-1。
步骤三:找到环,进行预处理。
步骤四:判断是否存在环,,有则继续下方步骤,无则跳出返回总和。
步骤五:将环变成一个点,并更新所有指向环里点的距离。
}
代码
#include <bits/stdc++.h>
#define ll long long
#define FAST_IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
ll n, m, i, j, t, w, sum, k, flag;
struct node {
ll be, en, va;
}a[2356];
//数组大小别想往下再减少
//别问为什么,问就是闲的
ll zhuliu(ll root) {
ll sum = 0, dis[n + 1], pre[n + 1], consume[n + 1], vis[n + 1];
while (true) {
for (ll i = 1; i <= n; i++)dis[i] = flag;//初始化
for (ll i = 1; i <= m; i++)
if (a[i].be != a[i].en && a[i].va < dis[a[i].en])
pre[a[i].en] = a[i].be, dis[a[i].en] = a[i].va;
//找到每个点除了根节点外每个点被指向的最短距离的线,记录下来
for (ll i = 1; i <= n; i++) {
if (i != root && dis[i] == flag)return -1;
vis[i] = consume[i] = 0;
//查看是否有点没有指向的边,没有跳出即可
//并完成初始化
}
ll cnt = 0;
for (ll i = 1; i <= n; i++) {
if (i == root)continue;
sum += dis[i];//记录长度
ll v = i;
while (vis[v] != i && v != root && !consume[v])
vis[v] = i, v = pre[v];
//查看这个点最后是否还会等于自己本身,也就是判断环
if (!consume[v] && v != root) {
//已经和根节点相连,那么就无需将其缩点
//已经缩点预处理过的无需再进行缩点预处理
consume[v] = ++cnt;
for (ll u = pre[v]; u != v; u = pre[u])
consume[u] = cnt;
}//为缩环做准备
}
if (!cnt)return sum;
for (ll i = 1; i <= n; i++)
if (!consume[i])consume[i] = ++cnt;
for (ll i = 1; i <= m; i++) {
t = a[i].en;//记录一下未更新前的下标
a[i].be = consume[a[i].be];
a[i].en = consume[a[i].en];
if (a[i].be != a[i].en)a[i].va -= dis[t];
//当这个两个点不在一个环中的时候
//最后已经确认为最佳路径的距离变成0,还未确认的点将距离缩一次
//个人wa点在这里,dis里的下标是未更新前的,需要提前存储
//更新的意义是在上次确认过最短路径,但不一定是最佳路径
//因此,为了在求和的时候不会出现重复累加的情况
//需要用指向此点可能最佳的路线长度减去最短路径长度
}
root = consume[root]; n = cnt;//更新一下被根节点以及总结点数
}
}
int main() {
FAST_IO;
while (cin >> n >> m >> k) {
for (i = flag = 1; i <= m; i++)cin >> a[i].be >> a[i].en >> a[i].va, flag += a[i].va;
cout << zhuliu(k) << endl;
}
}