洛谷邮递员送信

邮递员送信

题目描述

有一个邮递员要送东西,邮局在节点 1 1 1。他总共要送 n − 1 n-1 n1 样东西,其目的地分别是节点 2 2 2 到节点 n n n。由于这个城市的交通比较繁忙,因此所有的道路都是单行的,共有 m m m 条道路。这个邮递员每次只能带一样东西,并且运送每件物品过后必须返回邮局。求送完这 n − 1 n-1 n1 样东西并且最终回到邮局最少需要的时间。

输入格式

第一行包括两个整数, n n n m m m,表示城市的节点数量和道路数量。

第二行到第 ( m + 1 ) (m+1) (m+1) 行,每行三个整数, u , v , w u,v,w u,v,w,表示从 u u u v v v 有一条通过时间为 w w w 的道路。

输出格式

输出仅一行,包含一个整数,为最少需要的时间。

样例 #1

样例输入 #1

5 10
2 3 5
1 5 5
3 5 6
1 2 8
1 3 8
5 3 4
4 1 8
4 5 3
3 5 6
5 4 2

样例输出 #1

83

提示

对于 30 % 30\% 30% 的数据, 1 ≤ n ≤ 200 1 \leq n \leq 200 1n200

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 3 1 \leq n \leq 10^3 1n103 1 ≤ m ≤ 1 0 5 1 \leq m \leq 10^5 1m105 1 ≤ u , v ≤ n 1\leq u,v \leq n 1u,vn 1 ≤ w ≤ 1 0 4 1 \leq w \leq 10^4 1w104,输入保证任意两点都能互相到达。

思路分析

看到这道题,起初没有很好的想法,暴力+dijkstra堆优化,可以过几个点但是不能AC,摆出一开始的代码

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;
const int N = 2e3 + 10;
const int M = 2e5 + 10;
typedef pair<int, int> PII;

int e[M], ne[M], h[M], w[M], idx;
bool st[N][N];
int dis[N][N];
void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dijkstra(int x)
{ //得到x到其他点的最短距离
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    dis[x][x] = 0;
    heap.push({0, x});
    while (heap.size())
    {
        PII t = heap.top();
        heap.pop();
        int ver = t.second, distance = t.first;
        if(st[x][ver])continue;
        st[x][ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dis[x][j] > distance + w[i])
            {
                dis[x][j] = distance + w[i];
                heap.push({dis[x][j], j});
            }
        }
    }
}
int main()
{
    memset(h, -1, sizeof h);
    int n, m;
    cin >> n >> m;
    memset(dis, 0x3f, sizeof dis);

    for (int i = 0; i < m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }

    for (int i = 1; i <= n; i++)
    {
        dijkstra(i);
    }

    long long res = 0;
    for (int i = 2; i <= n; i++)
    {
        res += dis[1][i] + dis[i][1];
    }
    cout << res << endl;
    return 0;
}

后来看了题解,发现题目妙就妙在可以反向建图,详情看代码

#include <iostream>
#include <cstring>
#include <queue>

using namespace std;
const int N = 2e3 + 10;
const int M = 2e5 + 10;
typedef pair<int, int> PII;

int e[M], ne[M], h[M], w[M], idx;
bool st[N];
int dis[N];
void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dijkstra(int x)
{
    memset(dis, 0x3f, sizeof(dis));                      //初始化dis数组
    priority_queue<PII, vector<PII>, greater<PII>> heap; //初始化小根堆
    dis[x] = 0;                                          //记录出发点距离为0
    heap.push({0, x});                                   //进入优先队列
    while (heap.size())
    {                       //进入循环
        PII t = heap.top(); //记录队头元素
        heap.pop();         //队头元素出队
        int ver = t.second, distance = t.first;
        if (st[ver])
            continue;   //如果该点已经遍历过了,那么我们跳出本次循环
        st[ver] = true; //记录该点已经遍历
        for (int i = h[ver]; i != -1; i = ne[i])
        {                 //遍历该点能到达的点
            int j = e[i]; // j 是i能到达的点
            if (dis[j] > distance + w[i])
            { //更新距离
                dis[j] = distance + w[i];
                heap.push({dis[j], j});
            }
        }
    }
}
int main()
{
    memset(h, -1, sizeof h);
    int n, m;
    cin >> n >> m;

    for (int i = 0; i < m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
        add(b + n, a + n, c);
    }
    long long res = 0;
    dijkstra(1); //正着跑一次
    for (int i = 2; i <= n; i++)
        res += dis[i];
    dijkstra(1 + n); //反着跑一次
    //思路就在这里,如果暴力的话,这里要跑n次,复杂度上和flody差不多
    //反向建图的魅力就在于此,本来是从n到1的距离,反向建图以后就变成了1到n的距离,实在是妙哉!
    for (int i = 2 + n; i <= n << 1; i++)
        res += dis[i];
    cout << res << endl;
    return 0;
}

同样的把dijkstra换成spfa也可以过

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;
const int N = 210;
const int M = 210 * 210;

bool st[N];
int build[N];
int dis[N], e[M], ne[M], w[M], h[N], t[M], idx;
int add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, t[idx] = max(build[a], build[b]), ne[idx] = h[a], h[a] = idx++;
} // t[idx]表示该边在什么时刻有效
int spfa(int s, int d, int ti) //求ti时刻,s到d的距离
{
    memset(st, false, sizeof st);
    memset(dis, 0x3f, sizeof dis);
    dis[s] = 0;
    queue<int> q;
    q.push(s);
    st[s] = true;

    while (q.size())
    {
        int ver = q.front();
        q.pop();
        st[ver] = false;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            if (t[i] > ti)
            {
                continue;
            } //说明此时此路还没建好
            int j = e[i];
            if (dis[j] > dis[ver] + w[i])
            {
                dis[j] = dis[ver] + w[i];
                if (!st[j])
                {
                    st[j] = true;
                    q.push(j);
                }
            }
        }
    }
    if (dis[d] == 0x3f3f3f3f)
        return -1;
    else
        return dis[d];
}
int main()
{
    memset(h, -1, sizeof h);
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < n; i++)
        scanf("%d", &build[i]);
    for (int i = 0; i < m; i++)
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
        add(b, a, c);
    }
    int q;
    scanf("%d", &q);
    for (int i = 0; i < q; i++)
    {
        int x, y, t;
        scanf("%d%d%d", &x, &y, &t);
        cout << spfa(x, y, t) << endl;
    }
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值