bzoj 1576 [Usaco2009 Jan]安全路经Travel dijkstra+并查集/树链剖分

题面

题目传送门

解法

最短路树……emmmm,从未听说过……

  • 因为题目已经保证从 1 1 到其他所有点的最短路是唯一的,所以我们不妨把这些边全都记下来,然后就可以发现这些边共同构成一棵树
  • 首先考虑为什么这是一棵树。显然,我们只要说明这些边不会构成环即可。如果出现环,那么说明从1到环上的点一定由至少2条最短路,和题目描述出现矛盾,所以这一定是一棵树
  • 然后我们考虑,一条不在树上的边(x,y,v)会对哪些点的答案造成影响。显然,一定是树上 x x y路径上的所有点(不包括这两个点的 lca l c a )。假设路径上有一点 i i ,那么对i的影响就是 dis[x]+dis[y]dis[i]+v d i s [ x ] + d i s [ y ] − d i s [ i ] + v 。因为 dis[i] d i s [ i ] 对于 i i 是一个固定的值,所以我们只要求出每一个点对它造成影响的边(x,y,v) dis[x]+dis[y]+v d i s [ x ] + d i s [ y ] + v 最小的那一个就可以了
  • 这个显然可以用树剖+线段树维护吧,时间复杂度 O((m+n)logn+mlog2n) O ( ( m + n ) log ⁡ n + m log 2 ⁡ n ) ,可以通过此题
  • 我太懒了,不想写树剖
  • 但是我们可以发现,如果对于每一条不在树上的边 (x,y,v) ( x , y , v ) 按照 dis[x]+dis[y]+v d i s [ x ] + d i s [ y ] + v 从小到大排序,那么每一个点的答案一定会被第一次能影响到它的边更新,不必每一次都更新答案
  • 那么,我们就可以使用并查集来维护这一个过程,每一次在更新的时候可以直接跳过已经求出答案的部分
  • 时间复杂度: O((m+n)logn+mα(n)) O ( ( m + n ) log ⁡ n + m α ( n ) )

代码

#include <bits/stdc++.h>
#define int long long
#define PI pair <int, int>
#define mp make_pair
#define N 100010
using namespace std;
template <typename node> void chkmax(node &x, node y) {x = max(x, y);}
template <typename node> void chkmin(node &x, node y) {x = min(x, y);}
template <typename node> void read(node &x) {
    x = 0; int f = 1; char c = getchar();
    while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct Edge {
    int next, num, v;
} e[N * 5];
struct Node {
    int x, y, val;
    bool operator < (const Node &a) const {
        return val < a.val;
    }
} a[N * 5];
int n, m, cnt, p[N], fa[N], ans[N], dis[N], used[N];
void add(int x, int y, int v) {
    e[++cnt] = (Edge) {e[x].next, y, v};
    e[x].next = cnt;
}
void dijkstra(int s) {
    for (int i = 1; i <= n; i++)
        dis[i] = INT_MAX, used[i] = 0;
    priority_queue <PI, vector <PI>, greater <PI> > h;
    dis[s] = 0; h.push(mp(dis[s], s));
    while (!h.empty()) {
        PI tmp = h.top(); h.pop();
        int x = tmp.second;
        if (used[x]) continue; used[x] = 1;
        for (int p = e[x].next; p; p = e[p].next) {
            int k = e[p].num, v = e[p].v;
            if (dis[k] > dis[x] + v) {
                dis[k] = dis[x] + v, fa[k] = x;
                h.push(mp(dis[k], k));
            }
        }
    }
}
int Find(int x) {
    if (p[x] == x) return x;
    return p[x] = Find(p[x]);
}
main() {
    read(n), read(m); cnt = n;
    for (int i = 1; i <= m; i++) {
        int x, y, v; read(x), read(y), read(v);
        add(x, y, v);
    }
    dijkstra(1); int tot = 0;
    for (int i = 1; i <= n; i++)
        for (int p = e[i].next; p; p = e[p].next) {
            int k = e[p].num, v = e[p].v;
            a[++tot] = (Node) {i, k, dis[i] + dis[k] + v};
        }
    sort(a + 1, a + tot + 1);
    for (int i = 1; i <= n; i++) p[i] = i;
    memset(ans, -1, sizeof(ans));
    for (int i = 1; i <= tot; i++) {
        int x = a[i].x, y = a[i].y, v = a[i].val;
        if (x == fa[y] || y == fa[x]) continue;
        x = Find(x), y = Find(y);
        while (x != y) {
            if (dis[x] < dis[y]) swap(x, y);
            ans[x] = v - dis[x];
            p[x] = Find(fa[x]), x = Find(x);
        }
    }
    for (int i = 2; i <= n; i++) cout << ans[i] << "\n";
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值