[Usaco2009 Jan]安全路经Travel dijkstra + 并查集

19 篇文章 0 订阅
5 篇文章 0 订阅

这题确实非常好!对最短路径可以有更深刻的理解


。Gremlins最近在农场上泛滥,它们经常会阻止牛们从农庄(牛棚_1)走到别的牛棚(牛_i的目的 地是牛棚_i).每一个gremlin只认识牛_i并且知道牛_i一般走到牛棚_i的最短路经.所以它 们在牛_i到牛棚_i之前的最后一条牛路上等牛_i. 当然,牛不愿意遇到Gremlins,所以准备找 一条稍微不同的路经从牛棚_1走到牛棚_i.所以,请你为每一头牛_i找出避免gremlin_i的最 短路经的长度. 和以往一样, 农场上的M (2 <= M <= 200,000)条双向牛路编号为1..M并且能让所有牛到 达它们的目的地, N(3 <= N <= 100,000)个编号为1..N的牛棚.牛路i连接牛棚a_i (1 <= a_i <= N)和b_i (1 <= b_i <= N)并且需要时间t_i (1 <=t_i <= 1,000)通过. 没有两条牛路连接同样的牛棚,所有牛路满足a_i!=b_i.在所有数据中,牛_i使用的牛棚_1到牛 棚_i的最短路经是唯一的. 以下是一个牛棚,牛路和时间的例子: 1--[2]--2-------+ | | | [2] [1] [3] | | | +-------3--[4]--4 行程 最佳路经 最佳时间 最后牛路 p_1 到 p_2 1->2 2 1->2 p_1 到 p_3 1->3 2 1->3 p_1 到 p_4 1->2->4 5 2->4 当gremlins进入农场后: 行程 最佳路经 最佳时间 避免 p_1 到 p_2 1->3->2 3 1->2 p_1 到 p_3 1->2->3 3 1->3 p_1 到 p_4 1->3->4 6 2->4 20%的数据满足N<=200. 50%的数据满足N<=3000.

题目大意就是

给出一个图

从起点到每个点的最短路都是唯一的。

然后现在要找起点到每个点的次短路的长度。

要求每个点次短路径的最后一条边不能和该点的最短路的最后一条边一样。

题目中说了没有自环和重边


那么做法如下。

由于是到每个点的最短路都是唯一的

所以可以先进行dijstra(该题卡了SPFA) 求出每个点的最短路。

然后根据最短路就可以求出一个最短路径树。

上面的边都是所有最短路径中必须的边

那么我们加入任何一条非最短路径中的边,就会产生一个环,从而使得环上的点存在了次短路。

假设该边长度为w,两个端点为u和v,  u 到起点的距离是dis[u], v到起点的距离是dis[v]

那么令 len = dis[u] + dis[v] + w 。 在图上来看就是一个环的长度加上dis[LCA(u,v)] * 2。不过我们不需要管LCA

u的次短路就是len - dis[u]。 画一下便知道。

那么此时我们要想次短路最小。  由于dis[u]已经求出来了,所以要让len最小

那么我们便用所有的非最短路径上的边,求出一个len,按照len从小到大进行排序。

然后计算每个产生的环上的点的次短路即可


但是在计算过程中还是有些问题。

如果我们每次都挨个环上的点计算。 显然会TLE啊。

对于环上已经计算过的点,显然没必要去计算第二次了。因为我们已经计算过了。

那么这时候必须要借助一个东西,并查集了。

对于一个环上的点,我们计算完这上面的点之后,将所有的点的父亲设置为LCA(u,v)。

然后每次算的时候,从两个端点u,v同时开始计算,每次计算u或者v向其父亲结点移动。

就跟暴力求lca的过程有些相似。 慢慢两个点逐步移动到LCA(u, v)

中间如果碰到了计算过的点,直接移步到其刚才用并查集设置的父亲

这样就可以省略重复计算了!


#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdlib>
#include <cmath>
#include <map>
#include <sstream>
#include <queue>
#include <stack>
#include <vector>
#define MAXN 111111
#define MAXM 611111
#define PI acos(-1.0)
#define eps 1e-8
#define INF 1000000011
using namespace std;
typedef pair<int, int> P;
priority_queue<P, vector<P>, greater<P> >que;
int dis[MAXN];
struct Edge
{
    int v, next, w;
}edge[MAXM];
struct node
{
    int u, v, w;
    node(){}
    node(int _u, int _v, int _w){ u = _u; v = _v; w = _w;}
}cir[MAXM];
bool cmp(node a, node b)
{
    return dis[a.u] + dis[a.v] + a.w < dis[b.u] + dis[b.v] + b.w;
}
int head[MAXN], e;
int n, m;
int vis[MAXM];
int f[MAXN];
int deep[MAXN], fa[MAXN], pre[MAXN], ans[MAXN];
vector<int>g;
void init()
{
    memset(head, -1, sizeof(head));
    e = 0;
}
void add(int u, int v, int w)
{
    edge[e].v = v;
    edge[e].w = w;
    edge[e].next = head[u];
    head[u] = e++;
}
void dij()
{
    for(int i = 1; i <= n; i++) dis[i] = INF;
    dis[1] = 0;
    que.push(P(0, 1));
    while(!que.empty())
    {
        P p = que.top();
        que.pop();
        int u = p.second, d = p.first;
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].v;
            int w = edge[i].w;
            if(dis[v] > d + w)
            {
                dis[v] = d + w;
                que.push(P(dis[v], v));
            }
        }
    }
}
int find(int x)
{
    if(fa[x] == x) return x;
    int t = find(fa[x]);
    fa[x] = t;
    return t;
}
void dfs(int u, int dep)
{
    deep[u] = dep;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        int v = edge[i].v;
        int w = edge[i].w;
        if(dis[u] + w == dis[v])
        {
            vis[i] = vis[i ^ 1] = 1;
            pre[v] = u;
            dfs(v, dep + 1);
        }
    }
}
int main()
{
    int u, v, w;
    scanf("%d%d", &n, &m);
    init();
    for(int i = 0; i < m; i++)
    {
        scanf("%d%d%d", &u, &v, &w);
        add(u, v, w);
        add(v, u, w);
    }
    dij();
    dfs(1, 1);
    int cnt = 0;
    for(int i = 1; i <= n; i++) fa[i] = i;
    for(int i = 1; i <= n; i++)
        for(int j = head[i]; j != -1; j = edge[j].next)
        {
            if(!vis[j] && !vis[j ^ 1]) cir[cnt++] = node(i, edge[j].v, edge[j].w), vis[j] = vis[j ^ 1] = 1;
        }
    sort(cir, cir + cnt, cmp);
    memset(ans, -1, sizeof(ans));
    for(int i = 0; i < cnt; i++)
    {
        u = cir[i].u;
        v = cir[i].v;
        w = cir[i].w;
        g.clear();
        int len = dis[u] + dis[v] + w;

        while(u != v)
        {
            if(deep[u] > deep[v])
            {
                if(ans[u] != -1) u = find(u);
                else
                {
                    ans[u] = len - dis[u];
                    g.push_back(u);
                    u = pre[u];
                }
            }
            else
            {
                if(ans[v] != -1) v = find(v);
                else
                {
                    ans[v] = len - dis[v];
                    g.push_back(v);
                    v = pre[v];
                }
            }

        }
        int sz = g.size();
        for(int j = 0; j < sz; j++)
        {
            int t = find(g[j]);
            fa[t] = u;
        }
    }
    for(int i = 2; i <= n; i++) printf("%d\n", ans[i]);
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值