最短路树的简单应用 题目:(洛谷)P5201 [USACO19JAN]Shortcut G

本文介绍了一种利用Dijkstra算法构建最短路树的方法,并讨论了一道编程竞赛题目,该题目要求在图中添加一条边,使得所有牛从1号点出发的最短路径总和最小。解决方案包括先跑最短路,然后根据字典序最小的路径建树,最后通过DFS计算新边对总路径的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先什么是最短路树:最短路树是指通过Dijkstra算法在求出以某一个点为起点的最短路之后,以这个起点为树的根节点所构造的一颗树,这棵树满足如下条件,即:树上某点到根节点的路径对应原图上的一条最短路径。

题目链接:[USACO19JAN]Shortcut G - 洛谷

所以在知道上述知识后我们来看这道题,题目要求在一号点和某个点直接连一条边,使得牛在到所有牛在前往一号的过程中少走的路最大,题目强调了牛可以走新边的条件:(1)走了这条路会比原最短路径更短,(2)这条边出现在原最短路径的某个节点上。如果不满足第二个条件即使存在一条更短的路径牛也不会走,(3)牛在源图中的最短路径还需满足字典序最小的条件。

我们来分析如何解决这道题:首先我们先跑最短路,跑完之后我们考虑建树,因为牛走的路径一定是字典序最小的所以我们在建树的时候枚举这个点的每一条出边判断一下终点是否满足这个条件。

建完树之后我们得到的是一颗以1号点为根节点的深度递增的一颗有向树,那么我们在  i  号节点加一条到1号点的边那么受影响的只有i号点以及i号点的所有儿子们,那么加这条边所少走的路总和为: (这个节点以及下面儿子数量之和) * (i号点到一号点原来的最短路径长度 -  新边的长度)。

这样我们的单次查询的时间复杂度就是O(1)的所以整个算法的最大时间复杂的就是我们最开始跑的最短路。这样我们就轻松的AC这道题了

注意:long long

code:

#include <bits/stdc++.h>
#define N 1000010
#define int long long
using namespace std;
struct node {
    int f,t,nex,val;
}rt[N],rt1[N];
int head[N],head1[N];
int cnt,cnt1;
int a,b,vall;
int val[N];
int num[N];
int siz[N];
priority_queue<pair<int ,int > > p;

void add(int x,int y,int z ) {           //建原图的边
    rt[++ cnt].f = x;
    rt[cnt].t = y;
    rt[cnt].val = z;
    rt[cnt].nex = head[x];
    head[x] = cnt;
}
void add1(int x,int y) {               //建树的边
    rt1[++ cnt1].f = x;
    rt1[cnt1].t = y;
    rt1[cnt1].nex = head1[x];
    head1[x] = cnt1;
}
void dfs(int x) {                        //树上dfs统计儿子的个数
    siz[x] = num[x];
    for(int i = head1[x];i;i = rt1[i].nex) {
        dfs(rt1[i].t);
        siz[x] += siz[rt1[i].t];
    }
}
signed main() {
    cin>>a>>b>>vall;
    for(int i = 1;i <= a;i ++) {
        cin>>num[i];
    }
    int x,y,z;
    for(int i = 1;i <= b;i ++) {
        cin>>x>>y>>z;
        add(x,y,z);
        add(y,x,z);
    }
    memset(val,0x3f,sizeof val);
    val[1] = 0;
    p.push(make_pair(0,1));
    while(!p.empty()) {                             //先跑最短路
        int faq = p.top().second;
        p.pop();
        for(int i = head[faq];i;i = rt[i].nex)  {
            if(val[rt[i].t] > val[faq] + rt[i].val) {
                val[rt[i].t] = val[faq] + rt[i].val;
                p.push(make_pair(-val[rt[i].t],rt[i].t));
            }
        }
    }
    for(int i = 1;i <= a;i ++) {
        int minn = 99999999;
        for(int j = head[i];j;j = rt[j].nex) {               //我们开始建树(取min的操作就是满足一个字典序最小)
            if(val[rt[j].t] + rt[j].val == val[i]) {
                minn = min(minn,rt[j].t);
            }
        }
        if(minn <= a)
            add1(minn,i);
    }
    dfs(1);                  //dfs统计儿子的数量
    int maxx = 0;
    for(int i = 2;i <= a;i ++) {            //每次我们都计算一下这条新边的贡献
        int qaq = 0;
        qaq += (val[i] - vall) * siz[i];
        maxx = max(maxx,qaq);
    }
    cout<<maxx;
    return 0;
}
/*
5 6 2
1 2 3 4 5
1 2 5
1 3 3
2 4 3
3 4 5
4 5 2
3 5 7
 * */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值