【图论】新年好(最短路的综合问题)

本文详细解析了一道关于最短路算法的编程题,探讨了在数据范围较大时如何通过预处理优化算法以降低时间复杂度。通过Dijkstra算法预处理每个亲戚与起点的距离,并使用DFS遍历所有可能的拜访顺序,最终找到总时间最短的方案。文章强调了算法设计和细节处理的重要性,并提供了完整的C++代码实现。

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

一、前言

这道题目的时间复杂度卡的很死,需要在算法设计和细节上仔仔细细的考虑才能通过。非常考验对最短路算法的基础理解和灵活运用,是一道值得细细品味的图论题。

二、题面分析

题目链接:Acwing:新年好

重庆城里有 n 个车站,m 条 双向 公路连接其中的某些车站。

每两个车站最多用一条公路连接,从任何一个车站出发都可以经过一条或者多条公路到达其他车站,但不同的路径需要花费的时间可能不同。

在一条路径上花费的时间等于路径上所有公路需要的时间之和。

佳佳的家在车站 1,他有五个亲戚,分别住在车站 a,b,c,d,e。

过年了,他需要从自己的家出发,拜访每个亲戚(顺序任意),给他们送去节日的祝福。

怎样走,才需要最少的时间?

输入格式
第一行:包含两个整数 n,m,分别表示车站数目和公路数目。

第二行:包含五个整数 a,b,c,d,e,分别表示五个亲戚所在车站编号。

以下 m 行,每行三个整数 x,y,t,表示公路连接的两个车站编号和时间。

输出格式
输出仅一行,包含一个整数 T,表示最少的总时间。

数据范围
1≤n≤50000,
1≤m≤1e5,
1<a,b,c,d,e≤n,
1≤x,y≤n,
1≤t≤100
输入样例:
6 6
2 3 4 5 6
1 2 8
2 3 3
3 4 4
4 5 5
5 6 2
1 6 7
输出样例:
21

分析:第一眼反应以为是Floyd解题,然后看到数据范围直接傻了。然后想到先枚举所有可能的路线再依次求最短路,然后马上想到时间复杂度也不允许。在仔细想想后,我们找到一个突破口----亲戚一共只有5个,只要我们能先预处理好5个亲戚和自己的最短距离,然后再进行DFS遍历,时间复杂度就可以刚刚好达到我们的目的。但是即使思路是对的,在代码实现上也要万分小心。有很多细节都是要注意到。

三、代码详解

//#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
#define mem(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 7;

int n, m;
int p[6];  //简单的离散化处理
int h[N], e[N], ne[N], w[N], id = 1;
int dist[6][N];  //dist[i][j]:编号为i的点到点j的距离
bool ch[N];  //dijkstra查重
bool st[6];  //dfs查重

void add(int a, int b, int c)
{
    ne[id] = h[a];
    h[a] = id;
    e[id] = b;
    w[id] = c;
    id++;
}
int dfs(int step, int now, int res)  //步数、当前点的编号、已经走的距离
{
    if (step == 6)  //到6就说明走完5步了
    {
        return res;  //返回答案
    }
    int ans = INF;  //定义一个足够大的初始答案

    for (int i = 1; i <= 5; i++)  //枚举每一个亲戚
    {
        if (!st[i])  //如果这个点没走过
        {
            st[i] = true;
            ans = min(ans, dfs(step + 1, i, res + dist[now][p[i]]));   //找最小值
            st[i] = false;   //时光回溯
        }
    }
    return ans;   //返回最后的答案
}
void dijkstra(int s, int dist[ ])  //放进起点和到起点距离的数组,由于这里数组放进去的是地址,所以会直接修改全局变量
{
    mem(ch, 0);
    mem(dist, INF);
    priority_queue<PII, vector<PII>, greater<PII>> q;   //小根堆,不要写成了大根堆
    dist[s] = 0;  //起点距离为0
    q.push({ 0, s });  //放进优先队列
    while (q.size())
    {
        auto te = q.top();
        q.pop();
        int d = te.first;
        int idx = te.second;
        if (ch[idx] == 1)  //同一个点不被用来更新两次
            continue;
        ch[idx] = 1;
        for (int i = h[idx]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > d + w[i])
            {
                dist[j] = d + w[i];
                q.push({ dist[j], j });
            }
        }
    }
   
}

void solve()
{
    mem(dist, INF);
    mem(h, -1);
    cin >> n >> m;
    for (int i = 1; i <= 5; i++)
        cin >> p[i];
    while (m--)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }
    p[0] = 1;
    for (int i = 0; i <= 5; i++)  //5次dijkstra更新,多了一点都会tle掉
        dijkstra(p[i], dist[i]);
    cout << dfs(1, 0, 0);//DFS跑......
}

int main()
{
    std::ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值