(算法提高课)图论-单源最短路径问题2

1128. 信使
1127. 香甜的黄油

1128. 信使

战争时期,前线有 n个哨所,每个哨所可能会与其他若干个哨所之间有通信联系。
信使负责在哨所之间传递信息,当然,这是要花费一定时间的(以天为单位)。
指挥部设在第一个哨所。
当指挥部下达一个命令后,指挥部就派出若干个信使向与指挥部相连的哨所送信。
当一个哨所接到信后,这个哨所内的信使们也以同样的方式向其他哨所送信。信在一个哨所内停留的时间可以忽略不计。
直至所有 n个哨所全部接到命令后,送信才算成功。
因为准备充足,每个哨所内都安排了足够的信使(如果一个哨所与其他 k
个哨所有通信联系的话,这个哨所内至少会配备 k个信使)。
现在总指挥请你编一个程序,计算出完成整个送信过程最短需要多少时间。
输入格式
第 1行有两个整数 n和 m,中间用 1个空格隔开,分别表示有 n个哨所和 m条通信线路。
第 2至 m+1 行:每行三个整数 i、j、k,中间用 1个空格隔开,表示第 i个和第 j个哨所之间存在 双向 通信线路,且这条线路要花费 k天。
输出格式
一个整数,表示完成整个送信过程的最短时间。
如果不是所有的哨所都能收到信,就输出-1。
数据范围
1≤n≤100
1≤m≤200
1≤k≤1000
输入样例:
4 4
1 2 4
2 3 7
2 4 1
3 4 6
输出样例:
11
难度:简单
时/空限制:1s / 64MB
总通过数:10904
总尝试数:19519
来源:《信息学奥赛一本通》

本题可以看成求单源最短路径中到所有节点中的最大值
每个节点第一次收到广播,应该是消息从源头沿着最短路径到达该节点的时间(在题设背景下,只有1号节点是发布消息的,所以仍然可以看作单源头);
本题要求的是求节点最晚收到消息的时间,也即所有节点到源点距离最长的点所花费的时间,即为本题的解

这里注意一个判断,在图论操作中经常需要对数组或者邻接表做初始化操作(赋值为正无穷)
为了保证可加性,也即不溢出 dis[j] = dis[u] + g[u][j];我们通常将初始值设置为0x3f,注意memset赋值方法是按照字节赋值的,所以对int赋值的时候,每个int元素会被赋值为0x3f3f3f3f而不是0x3f

//本题看似不是单源最短路径而是多源问题
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
const int INF = 0x3f;
int n, m;
int g[N][N];
bool vis[N];
int dis[N];

int dijkstra()
{
    int res = 0;
    dis[1] = 0;
    for(int i = 0;i < n;i ++){
        int u = -1;
        for(int j = 1;j <= n;j ++){
            if(!vis[j] && (u == -1 || dis[j] < dis[u])){
                u = j;
            }
        }
        vis[u] = 1;
        for(int j = 1;j <= n;j ++){
            if(!vis[j] && dis[j] > dis[u] + g[u][j]){
                dis[j] = dis[u] + g[u][j];
            }
        }
    }
    for(int j = 2;j <= n;j ++) res = max(res, dis[j]);
    if(res == 0x3f3f3f3f) res = -1;   //这里要做一个判断,不保证源点一定可以到所有节点
    return res;
}

int main()
{
    memset(g, INF, sizeof g);
    memset(dis, INF, sizeof dis);
    cin >> n >> m;
    while(m --){
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = c;
    }
    int ans = dijkstra();
    cout << ans << endl;
    return 0;
}

1127. 香甜的黄油

农夫John发现了做出全威斯康辛州最甜的黄油的方法:糖。
把糖放在一片牧场上,他知道 N 只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。
当然,他将付出额外的费用在奶牛上。
农夫John很狡猾,就像以前的巴甫洛夫,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。
他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。
农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。
给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)。
数据保证至少存在一个牧场和所有牛所在的牧场连通。
输入格式
第一行: 三个数:奶牛数 N,牧场数 P,牧场间道路数 C。
第二行到第 N+1 行: 1 到 N 头奶牛所在的牧场号。
第 N+2 行到第 N+C+1 行:每行有三个数:相连的牧场A、B,两牧场间距 D,当然,连接是双向的。
输出格式
共一行,输出奶牛必须行走的最小的距离和。
数据范围
1≤N≤500
2≤P≤800
1≤C≤1450
1≤D≤255
输入样例:
3 4 5
2
3
4
1 2 1
1 3 5
2 3 7
2 4 3
3 4 5
输出样例:
8

自己写的版本是遍历,将所有节点都设置为源节点计算一次,能跑通部分数据但TLE了

#include<bits/stdc++.h>
using namespace std;
const int N = 810;
const int INF = 0x3f;
const int MAX = 0x3f3f3f3f;
int n, m, num;
int g[N][N];
int init[N];  //记录初始在i牧场的奶牛有哪些
int dis[N];
int vis[N];

//以st节点(牧场)作为源头
int dijkstra(int st)
{
    memset(dis, INF, sizeof dis);
    memset(vis, 0, sizeof vis);
    
    dis[st] = 0;
    
    for(int i = 0;i < m;i ++){
        int u = -1;
        for(int j = 1;j <= m;j ++){
            if(!vis[j] && (u == -1 || dis[j] < dis[u])){
                u = j;
            }
        }
        vis[u] = 1;
        if(dis[u] == MAX) return MAX;  //此时源头节点到并非保证联通
        for(int j = 1;j <= m;j ++){
            if(!vis[j] && dis[j] > dis[u] + g[u][j]){
                dis[j] = dis[u] + g[u][j];
            }
        }
    }
    /*
    cout << st << endl;
    for(int i = 1;i <= m;i ++)
        cout << dis[i] <<" ";
    cout << endl;*/
    int res = 0;
    for(int i = 1;i <= m;i ++){
        int x = init[i]; //当前牧场的奶牛数目
        res += dis[i] * x;
    }
    //cout << res << endl;
    return res;
}
int main()
{
    memset(g, INF, sizeof g);
    cin >> n >> m >> num;
    for(int i = 1;i <= n;i ++){
        int x; scanf("%d", &x);
        init[x] ++;
    }
    while(num --){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        g[a][b] = g[b][a] = c;
    }
    int ans = MAX;
    for(int i = 1;i <= m;i ++){
        ans = min(ans, dijkstra(i));
    }
    cout << ans;
    return 0;
}

改进:时间复杂度在第二层循环,也即寻找距离最近的点的时候可以优化,也即采用堆优化法来找到距离最近的节点
在堆优化法中用邻接表来存储边

邻接表声明

int h[N], e[M], w[M], ne[M], idx;  //N为节点个数,M为边的个数
其中
h[a] 指向a节点起点的邻接表列表的最后一个元素
e[idx]  为当前idx编号的边指向的节点
w[idx]  为当前idx编号的边的权重
ne   存储邻接表链表,当前值对于邻接表下一个的地址,类似于值为指针

初始化
idx = 0;
memset(h, -1, sizeof h);

邻接表构建

void add(int a, int b, int c) {
    e[idx] = b;
    w[idx] = c;   
    //以上的两步构建了一条边,这条边只存储了终点和权值(因为邻接表的表头就是起点,所以不存储无用信息)
    ne[idx] = h[a];  //类似于头插法 node->next = L->next(当前节点的next指针指向头节点的下一个节点)
    h[a] = idx ++;   //L->next = node; idx++ 将当前的头节点的next节点设置为当前节点
 }
 
1. 在这种构建方式中, idx为边的序号,作为边的唯一标识,找到了idx就可以读到该条边的终点信息和权值信息
2. 邻接表的构造方式和头插法类似,h[a]指向最新录入的以a节点为起点的边的信息,其存储的值是一个边的idx,通过ne[idx]可以获得当前边的下一条的边的idx值;ne[idx] == -1 与 node->next == NULL类似,此时遍历到了该链表的尾部节点
3. 如果指向下一个为空时,指针值为-1

邻接表遍历


for(int i = h[vel]; i != -1; i = ne[i]) { 
    //TODO
}
 
i = ne[i] 模拟链表指针的next操作
h[vel] 指向vel链表的最后一个,遍历是从后往前的

在这里插入图片描述
形成的邻接表如下:
在这里插入图片描述

优化后代码如下:

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N = 810;  //点的数量
const int M = 3010;
const int INF = 0x3f;
const int MAX = 0x3f3f3f3f;

int n, m, num;
int cow[N]; //记录每头奶牛的初始牧场位置
int h[N], e[M], w[M], ne[M], idx;
bool vis[N];
int dis[N];

void add(int a, int b, int c)
{
    e[idx] = b;  //该边的to为b节点
    w[idx] = c;   //改边的边权为c
    ne[idx] = h[a];
    h[a] = idx ++;
}

int dijkstra(int st)
{
    memset(vis, 0, sizeof vis);
    memset(dis, INF, sizeof dis);
    dis[st] = 0;
    priority_queue<PII, vector<PII>, greater<PII> >heap;
    heap.push({0, st});
    while(!heap.empty()){
        int u = heap.top().second;  //此时u即为当前距离源点最近的节点
        heap.pop();
        if(vis[u]) continue;
        vis[u] = true;
        for(int i = h[u]; i != -1; i = ne[i]){
            //找到和u相连的节点,遍历更新dis的值
            int j = e[i];
            if(dis[j] > dis[u] + w[i]){
                dis[j] = dis[u] + w[i];
                heap.push({dis[j], j});
            }
        }
    }
    int ans = 0;
    for(int i = 1;i <= n;i ++){   //计算N个节点的总消费
        if(dis[cow[i]] == MAX) return MAX;
        ans += dis[cow[i]];
    }
    return ans;
}
int main()
{
    memset(h, -1, sizeof h);
    cin >> n >> m >> num;
    for(int i = 1;i <= n;i ++) cin >> cow[i];
    while(num --){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
        add(b, a, c);
    }
    int ans = MAX;
    for(int i = 1;i <= m;i ++){
        ans = min(ans, dijkstra(i));
    }

    cout << ans << endl;
    return 0;
}

堆优化的时间复杂度更低,同时它适用于点多边少的情况(稀疏),此时用邻接矩阵将存储大量无用信息)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值