单源最短路的综合应用

简介: 笔记

Acwing 1135 新年好


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


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


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


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


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


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


输入格式

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


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


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


输出格式

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


数据范围

2.png


思路

本题会卡掉spfa 所以用堆优化的Dijkstra做


先预处理 每个点到其他点的最短距离


再dfs暴力枚举访问的顺序 取最小值


代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int>PII;
const int N = 500010, INF = 0x3f3f3f3f;
int n, m;
int dist[6][N];
int source[6];
bool st[N];
vector<pair<int, int>>vec[N];
void dijkstra(int start, int dist[]) {
  memset(dist, 0x3f, N * 4);
  dist[start] = 0;
  memset(st, false, sizeof st);
  priority_queue<PII, vector<PII>, greater<PII>>heap;
  heap.push({ 0,start });
  while (!heap.empty()) {
    auto t = heap.top();
    heap.pop();
    int ver = t.second;
    if (st[ver])continue;
    st[ver] = true;
    for (int i = 0; i < vec[ver].size(); ++i) {
      int j = vec[ver][i].second;
      int w = vec[ver][i].first;
      if (dist[j] > dist[ver] + w) {
        dist[j] = dist[ver] + w;
        heap.push({ dist[j],j });
      }
    }
  }
}
int dfs(int u, int start, int distance) {
  if (u == 6)return distance;
  int res = 0x3f3f3f3f;
  for (int i = 1; i <= 5; ++i) {
    int next_ = source[i]; //下一个点的编号
    if (!st[i]) {
      st[i] = true;
      res = min(res, dfs(u + 1, i, distance + dist[start][next_]));
      st[i] = false;
    }
  }
  return res;
}
int main() {
  cin >> n >> m;
  source[0] = 1;
  for (int i = 1; i <= 5; ++i)scanf("%d", &source[i]);
  while (m--) {
    int u, v, w; scanf("%d%d%d", &u, &v, &w);
    vec[u].push_back({ w,v });
    vec[v].push_back({ w,u });
  }
  for (int i = 0; i < 6; ++i) {
    dijkstra(source[i], dist[i]);
  }
  memset(st, false, sizeof st);
  printf("%d\n", dfs(1, 0, 0));
  return 0;
}


AcWing 340. 通信线路


在郊区有 N 座通信基站,P 条 双向 电缆,第 i 条电缆连接基站A i 和B i

特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。


现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费L i


电话公司正在举行优惠活动。


农产主可以指定一条从 1 号基站到 N 号基站的路径,并指定路径上不超过 K 条电缆,由电话公司免费提供升级服务。


农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。


求至少用多少钱可以完成升级。


输入格式

第1行:三个整数N,P,K。


第2…P+1行:第 i+1 行包含三个整数A i , B i , L i  


输出格式

包含一个整数表示最少花费。


若1号基站与N号基站之间不存在路径,则输出”-1”。


数据范围

0≤K<N≤1000

1≤P≤10000

1≤Li≤1000000


思路

题意为求一条从1-n的路径上的第k+1大的边的权值的最小值


因为是求最大值的最小值 可以想到用二分做


二分第k+1大的边的权值 然后把 大于这条边的边权值设置为1 小于这条边的边权值设置为0


整个图中只有权值为0/1的边 所以可以用双端队列广搜做


当dist[n]>k时 说明mid的值偏小


当dist[n]<=k时 说明mid的值可能为答案或者偏大


代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int>PII;
const int N = 1010;
int n, m, k;
vector<PII>vec[N];
deque<int>q;
bool st[N];
int dist[N];
bool check(int bound) {
  memset(st, false, sizeof st);
  memset(dist, 0x3f, sizeof dist);
  q.push_back(1);
  dist[1] = 0;
  while (!q.empty()) {
    auto t = q.front();
    q.pop_front();
    if (st[t])continue;
    st[t] = true;
    for(int i = 0 ;i < vec[t].size();++i){
      int j = vec[t][i].second;
      int v = vec[t][i].first > bound;
      if (dist[j] > dist[t] + v) {
        dist[j] = dist[t] + v;
        if (!v) q.push_front(j);
        else q.push_back(j);
      }
    }
  }
  return dist[n] <= k;
}
int main() {
  cin >> n >> m >> k;
  while (m--) {
    int u, v, w; cin >> u >> v >> w;
    vec[u].push_back({ w,v });
    vec[v].push_back({ w,u });
  }
  int l = 0, r = 1e6 + 1;
  while (l < r) {
    int mid = l + r >> 1;
    if (check(mid))r = mid;
    else l = mid + 1;
  }
  if (r == 1e6 + 1)r = -1;
  cout << r << endl;
  return 0;
}


AcWing 342. 道路与航线


农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。


他想把牛奶送到T个城镇,编号为1~T。


这些城镇之间通过R条道路 (编号为1到R) 和P条航线 (编号为1到P) 连接。


每条道路 i 或者航线 i 连接城镇A i到B i ,花费为C i


对于道路,0 ≤ C i ≤ 10 , 000 ;然而航线的花费很神奇,花费Ci可能是负数(− 10 , 000 ≤ C i ≤ 10 , 000 )。


道路是双向的,可以从A i到B i ,也可以从B i到A i ,花费都是C i 然而航线与之不同,只可以从A i 到B i

事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从A i 到B i ,那么保证不可能通过一些道路和航线从B i  回到A i


由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。


他想找到从发送中心城镇S把奶牛送到每个城镇的最便宜的方案。


输入格式

第一行包含四个整数T,R,P,S。


接下来R行,每行包含三个整数(表示一个道路)A i , B i , C i


接下来P行,每行包含三个整数(表示一条航线)A i , B i , C i


输出格式

第1…T行:第 i 行输出从 S 到达城镇 i 的最小花费,如果不存在,则输出“NO PATH”。


数据范围

1 ≤ T ≤ 25000

1 ≤ R , P ≤ 50000

1 ≤ A i , B i , S ≤ T


思路

dijkstra+topsort


没有负权边的图可以用Dijkstra 有向无环图不管边权正负都可以用SPFA


1.先输入所有双向道路 dfs出所有连通块 计算两个数组:id[]存储每个点属于哪个连通块;$vectorblock[] $存储每个连通块里有哪些点;


2.输入所有航线,同时统计出每个连通块的入度


3.按照拓扑序依次处理每个连通块。先将所有入度为0的连通块的编号加入队列中


4.每次从队头取出一个连通块的编号bid


5.将该]block[bid]中的所有点加入堆中,然后对堆中所有点跑Dijkstra算法


6.每次取出堆中距离最小的点的ver


7.然后遍历ver的所有邻点j jj。如果id[ver]==id[j]那么如果j能被更新 将j插入堆中;如果 id[ver] != id[j]


则将]id[j] 这个连通块的入度减1,如果减成0了,则将其插入拓扑排序的队列中


代码

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
typedef pair<int, int>PII;
const int N = 25010;
int n, mr, mp, S;
int id[N], dist[N], din[N];
vector<int>block[N];
vector<PII>vec[N];
bool st[N];
int bcnt;
queue<int>q;
void dijkstra(int bid) {
  priority_queue<PII, vector<PII>, greater<PII>>heap;
  for (int i = 0; i < block[bid].size(); ++i) {
    int j = block[bid][i];
    heap.push({ dist[j],j });
  }
  while (heap.size()) {
    auto t = heap.top();
    heap.pop();
    int ver = t.second;
    if (st[ver])continue;
    st[ver] = true;
    for (int i = 0; i < vec[ver].size(); ++i) {
      int j = vec[ver][i].second;
      int dis = vec[ver][i].first;
      if (dist[j] > dist[ver] + dis) {
        dist[j] = dist[ver] + dis;
        if (id[j] == id[ver])heap.push({ dist[j],j });
      }
      if (id[j] != id[ver] && --din[id[j]] == 0)q.push(id[j]);
    }
  }
}
void topsort() {
  memset(dist,  0x3f, sizeof dist);
  dist[S] = 0;
  for (int i = 1; i <= bcnt; ++i) {
    if (!din[i])
      q.push(i);
  }
  while (q.size()) {
    int t = q.front();
    q.pop();
    dijkstra(t);
  }
}
void dfs(int u, int bid) {
  id[u] = bid;
  block[bid].push_back(u);
  for (int i = 0; i < vec[u].size(); ++i) {
    int j = vec[u][i].second;
    if (!id[j])
      dfs(j, bid);
  }
}
int main() {
  cin >> n >> mr >> mp >> S;
  while (mr--) {
    int u, v, w;
    cin >> u >> v >> w;
    vec[u].push_back({ w,v });
    vec[v].push_back({ w,u });
  }
  for (int i = 1; i <= n; ++i) {
    if (!id[i])
      dfs(i, ++bcnt);
  }
  while (mp--) {
    int u, v, w;
    cin >> u >> v >> w;
    vec[u].push_back({ w,v });
    din[id[v]]++;
  }
  topsort();
  for (int i = 1; i <= n; ++i) {
    if (dist[i] > INF / 2)puts("NO PATH");
    else printf("%d\n", dist[i]);
  }
  return 0;
}


AcWing 341 最优贸易


C国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。


任意两个城市之间最多只有一条道路直接相连。


这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1条。


C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。


但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。


商人阿龙来到C国旅游。


当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。


设C国 n 个城市的标号从 1~n,阿龙决定从1号城市出发,并最终在 n 号城市结束自己的旅行。


在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。


阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。


因为阿龙主要是来C国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。


现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。


请你告诉阿龙,他最多能赚取多少旅费。


注意:本题数据有加强。


输入格式

第一行包含 2 个正整数 n 和 m,中间用一个空格隔开,分别表示城市的数目和道路的数目。


第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n 个城市的商品价格。


接下来 m 行,每行有 3 个正整数,x,y,z,每两个整数之间用一个空格隔开。


如果z=1,表示这条道路是城市 x 到城市 y 之间的单向道路;如果z=2,表示这条道路为城市 x 和城市 y 之间的双向道路。


输出格式

一个整数,表示答案。


数据范围

1≤n≤100000

1≤m≤500000

1≤各城市水晶球价格≤100


思路

dmin[k] 表示1−k 价格的最小值dmax[k] 表示 k−n 价格的最大值


SPFA求出dmin和dmax后 遍历1−n 求出dmax[k]−dmin[k] 的最大值


在求dmin和dmax时 需要建反图


用Dijkstra会超时 所以用SPFA


代码

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m;
int w[N];
int dmin[N], dmax[N];
vector<int>vec1[N];
vector<int>vec2[N];
queue<int>q;
bool st[N];
void spfa(vector<int>vec[], int dist[], int type) {
  if (type == 0) {
    memset(dist, 0x3f, sizeof dmin);
    dist[1] = w[1];
    q.push(1);
  }
  else {
    memset(dist, 0, sizeof dmax);
    dist[n] = w[n];
    q.push(n);
  }
  while (q.size()) {
    int t = q.front();
    q.pop();
    st[t] = false;
    for (int i = 0; i < vec[t].size(); ++i) {
      int j = vec[t][i];
      if ((type == 0 && dist[j] > min(dist[t], w[j])) || (type == 1 && dist[j] < max(dist[t], w[j]))) {
        if (type == 0) dist[j] = min(dist[t], w[j]);
        else dist[j] = max(dist[t], w[j]);
        if (!st[j]) {
          q.push(j);
          st[j] = true;
        }
      }
    }
  }
}
int main() {
  cin >> n >> m;
  for (int i = 1; i <= n; ++i)scanf("%d", &w[i]);
  while (m--) {
    int u, v, w;
    scanf("%d%d%d", &u, &v, &w);
    vec1[u].push_back(v);
    vec2[v].push_back(u);
    if (w == 2) {
      vec1[v].push_back(u);
      vec2[u].push_back(v);
    }
  }
  spfa(vec1, dmin, 0);
  spfa(vec2, dmax, 1);
  int res = -1;
  for (int i = 1; i <= n; ++i) {
    res = max(res, dmax[i] - dmin[i]);
  }
  cout << res << endl;
  return 0;
}


目录
相关文章
|
算法
单源最短路的拓展应用
单源最短路的拓展应用
73 0
|
11月前
|
算法 搜索推荐
算法分析 | 第一套(渐近分析)
算法分析 | 第一套(渐近分析)
62 0
单源最短路的综合应用
单源最短路的综合应用
56 0
5 状态压缩Dp及其衍生
5 状态压缩Dp及其衍生
68 0
|
算法 C++
<<算法很美>>——(五)——回溯算法核心框架(上)
<<算法很美>>——(五)——回溯算法核心框架(上)
<<算法很美>>——(五)——回溯算法核心框架(上)
【夯实算法基础】树形DP入门详解+多道例题剖析
【夯实算法基础】树形DP入门详解+多道例题剖析
【夯实算法基础】树形DP入门详解+多道例题剖析
【夯实算法基础】差分约束
【夯实算法基础】差分约束
【夯实算法基础】差分约束
【夯实算法基础】 并查集
【夯实算法基础】 并查集
【夯实算法基础】 并查集
【算法提高——第三讲(一)】图论(1)
【算法提高——第三讲(一)】图论(1)
【算法提高——第三讲(一)】图论(1)
【算法提高——第三讲(二)】图论(1)
【算法提高——第三讲(二)】图论(1)
【算法提高——第三讲(二)】图论(1)