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