本周学习最短路径问题
1、
题目:洛谷B3647 【模板】Floyd
给出一张由 n 个点 m 条边组成的无向图。求出所有点对 (i,j) 之间的最短路径。输入第一行为两个整数n,m,分别代表点的个数和边的条数。接下来 m 行,每行三个整数 u,v,w,代表 u,v 之间存在一条边权为 w 的边。输出 n 行每行 n 个整数。第 i 行的第 j 个整数代表从 i 到 j 的最短路径。
思路:
用floyd算法,解决图中所有顶点对之间最短路径
#include
#include
#include
using namespace std;
const int INF = INT_MAX;
void floydWarshall(vector >& graph, int n) {
// 使用邻接矩阵表示图,初始化距离矩阵
vector > dist(n, vector(n, INF));
// 将已知的边权值填入距离矩阵
for (int i = 0; i < n; ++i) {
dist[i][i] = 0;
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (i == j) continue;
dist[i][j] = min(dist[i][j], graph[i][j]);
}
}
// 使用 Floyd-Warshall 算法计算最短路径
for (int k = 0; k < n; ++k) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (dist[i][k] != INF && dist[k][j] != INF) {
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
}
// 输出结果
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cout << (dist[i][j] == INF ? -1 : dist[i][j]) << " ";
}
cout << endl;
}
}
int main() {
int n, m;
cin >> n >> m;
// 初始化邻接矩阵
vector > graph(n, vector(n, INF));
// 读入边的权值
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
graph[u - 1][v - 1] = w;
graph[v - 1][u - 1] = w; // 无向图,需要考虑双向
}
// 调用 Floyd-Warshall 算法计算最短路径
floydWarshall(graph, n);
return 0;
}
2、
题目:洛谷P4779 【模板】单源最短路径(标准版)
给定一个 n 个点,m 条有向边的带非负权图,请你计算从s 出发,到每个点的距离。数据保证你能从 s 出发到任意点。输入第一行为三个正整数n,m,s。 第二行起 m 行,每行三个非负整数 ui,vi,wi,表示从 ui 到 vi 有一条权值为 wi 的有向边。输出一行 n 个空格分隔的非负整数,表示 s 到每个点的距离。
思路:
使用邻接表来表示图。通过数组 fr
记录每个顶点的邻接表的起始位置,数组 to
存储边的目标顶点,nex
存储下一条边在邻接表中的位置,v
存储边的权重。用add函数添加一条边到图中,更新邻接表。进行Dijkstra算法初始化,使用优先队列不断选择当前最短路径估计的顶点,放松其邻接点,更新最短路径估计。重复此过程直到所有顶点都被访问。
#include
#define M(x,y) make_pair(x,y)
using namespace std;
int fr[100010],to[200010],nex[200010],v[200010],tl,d[100010];
bool b[100010];
void add(int x,int y,int w){
to[++tl]=y;
v[tl]=w;
nex[tl]=fr[x];
fr[x]=tl;
}
priority_queue< pair > q;
int main(){
int n,m,x,y,z,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
for(int i=1;i<=n;i++) d[i]=1e10;
d[s]=0;
q.push(M(0,s));
while(!q.empty()){
int x=q.top().second;
q.pop();
if(b[x]) continue;
b[x]=1;
for(int i=fr[x];i;i=nex[i]){
int y=to[i],l=v[i];
if(d[y]>d[x]+l){
d[y]=d[x]+l;
q.push(M(-d[y],y));
}
}
}
for(int i=1;i<=n;i++) printf("%d ",d[i]);
return 0;
}
3、
题目:洛谷P2661 [NOIP2015 提高组] 信息传递
有 n 个同学(编号为 1 到 n)正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 Ti 的同学。游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自己的生日时,游戏结束。请问该游戏一共可以进行几轮?输入共 2 行。第一行包含 1 个正整数 n,表示 n 个人。第二行包含 n 个用空格隔开的正整数 T1,T2,⋯,Tn,其中第 i 个整数 Ti 表示编号为 i 的同学的信息传递对象是编号为 Ti 的同学,Ti≤n 且Ti ≠i。输出共一行一个整数,表示游戏一共可以进行多少轮。
思路:
用并查集找到信息传递的环,记录最小的环的长度遍历每个同学,通过 get
函数找到信息传递的环,如果环的起点是当前同学(get(f, cnt) == i
),则更新答案为最小的环长度。
#include
using namespace std;
const int N = 200010;
int n, fa[N], ans = 0x3f3f3f3f;
int get (int x, int &cnt) { //cnt记录环的长度
cnt ++;
if (fa[x] == x) return x;
else return get(fa[x], cnt);
}
int main () {
scanf("%d", &n);
for (int i = 1; i <= n; i ++)
fa[i] = i;
for (int i = 1; i <= n; i ++) {
int cnt = 0, f;
scanf("%d", &f);
if (get(f, cnt) == i) {
ans = min(ans, cnt); //维护最小的环
}else
fa[i] = f;
}
printf("%d", ans);
return 0;
}
4、
题目:洛谷P1144 最短路计数
给出一个 N 个顶点 M 条边的无向无权图,顶点编号为1∼N。问从顶点 1 开始,到其他每个点的最短路有几条。输入第一行包含 2 个正整数 N,M,为图的顶点数与边数。接下来 M 行,每行 2 个正整数 x,y,表示有一条由顶点 x 连向顶点 y 的边,请注意可能有自环与重边。输出共 N 行,每行一个非负整数,第 i 行输出从顶点 1 到顶点 i 有多少条不同的最短路,由于答案有可能会很大,你只需要输出 ans mod 100003 后的结果即可。如果无法到达顶点 i 则输出 0。
思路:
使用广度优先搜索(BFS)和动态规划,通过邻接表 graph
表示图的连接关系。使用两个数组 distance
和 count
分别记录从顶点1到每个顶点的最短路径长度和路径数目,利用BFS遍历图,从顶点1开始,逐层遍历,记录每个顶点的最短路径长度和路径数目。如果发现一个顶点的最短路径长度等于当前节点的最短路径长度加1,说明存在另一条最短路径,那么将路径数目加到该节点上。如果发现一个顶点的最短路径长度为-1,说明从1无法到达该节点,直接输出0。最后输出每个顶点的最短路径数目,如果无法到达,输出0。
#include
#include
#include
using namespace std;
const int MOD = 100003;
void addMod(int& a, int b) {
a = (a + b) % MOD;
}
int main() {
int N, M;
cin >> N >> M;
// 邻接表表示图
vector > graph(N + 1);
for (int i = 0; i < M; ++i) {
int x, y;
cin >> x >> y;
graph[x].push_back(y);
graph[y].push_back(x); // 无向图,需要加上反向边
}
// BFS
vector distance(N + 1, -1);
vector count(N + 1, 0);
queue q;
distance[1] = 0;
count[1] = 1;
q.push(1);
while (!q.empty()) {
int current = q.front();
q.pop();
for (int neighbor : graph[current]) {
if (distance[neighbor] == -1) {
distance[neighbor] = distance[current] + 1;
count[neighbor] = count[current];
q.push(neighbor);
} else if (distance[neighbor] == distance[current] + 1) {
addMod(count[neighbor], count[current]);
}
}
}
// 输出结果
for (int i = 1; i <= N; ++i) {
if (distance[i] == -1) {
cout << 0 << endl;
} else {
cout << count[i] << endl;
}
}
return 0;
}
5、
题目:洛谷P8794 [蓝桥杯 2022 国 A] 环境治理
LQ 国拥有 n 个城市,从 0 到 n−1 编号,这 n 个城市两两之间都有且仅有一条双向道路连接,这意味着任意两个城市之间都是可达的。每条道路都有一个属性 D,表示这条道路的灰尘度。当从一个城市 A 前往另一个城市 B 时,可能存在多条路线,每条路线的灰尘度定义为这条路线所经过的所有道路的灰尘度之和,LQ 国的人都很讨厌灰尘,所以他们总会优先选择灰尘度最小的路线。
LQ 国很看重居民的出行环境,他们用一个指标 P 来衡量 LQ 国的出行环境,P 定义为:
P=i=0∑n−1j=0∑n−1d(i,j)
其中 d(i,j) 表示城市 i 到城市 j 之间灰尘度最小的路线对应的灰尘度的值。
为了改善出行环境,每个城市都要有所作为,当某个城市进行道路改善时,会将与这个城市直接相连的所有道路的灰尘度都减少 1,但每条道路都有一个灰尘度的下限值 L,当灰尘度达到道路的下限值时,无论再怎么改善,道路的灰尘度也不会再减小了。
具体的计划是这样的:
……
……
LQ 国想要使得 P 指标满足P≤Q。请问最少要经过多少天之后,P 指标可以满足 P≤Q。如果在初始时就已经满足条件,则输出 0;如果永远不可能满足,则输出−1。输入的第一行包含两个整数 n,Q,用一个空格分隔,分别表示城市个数和期望达到的 P 指标。接下来 n 行,每行包含 n 个整数,相邻两个整数之间用一个空格分隔,其中第 i 行第 j 列的值 Di,j(Di,j=Dj,i,Di,i=0) 表示城市 i 与城市 j 之间直接相连的那条道路的灰尘度。接下来 n 行,每行包含 n 个整数,相邻两个整数之间用一个空格分隔,其中第 i 行第 j 列的值 Li,j(Li,j=Lj,i,Li,i=0) 表示城市 i 与城市 j 之间直接相连的那条道路的灰尘度的下限值。输出一行包含一个整数表示答案。
思路:
使用二分搜索来找到达到目标污染水平所需的最小天数,通过使用 calculatePollution
函数模拟污染水平的变化,函数根据天数调整污染值,并使用 Floyd-Warshall 算法在城市网络中找到最短路径。
#include
#define MAX_N 105
#define INF 1000000000
int pollution[MAX_N][MAX_N];
int pollutionLimit[MAX_N][MAX_N];
int n;
int min(int a, int b) {
return a < b ? a : b;
}
//计算经过一定天数后的污染水平
int calculatePollution(int days) {
int temp[MAX_N][MAX_N];
// 使用初始污染值初始化辅助矩阵
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
temp[i][j] = pollution[i][j];
// 使根据天数调整污染值
for (int i = 0; i < n; i++) {
int val = days / n + (days % n >= i + 1 ? 1 : 0);
for (int j = 0; j < n; j++) {
temp[i][j] -= val;
if (temp[i][j] < pollutionLimit[i][j]) temp[i][j] = pollutionLimit[i][j];
temp[j][i] -= val;
if (temp[j][i] < pollutionLimit[j][i]) temp[j][i] = pollutionLimit[j][i];
}
}
// 使用 Floyd-Warshall 算法找到最短路径
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
temp[i][j] = min(temp[i][j], temp[i][k] + temp[k][j]);
// 计算治理后的总污染值
int result = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
result += temp[i][j];
return result;
}
int main() {
int targetPollution;
scanf("%d %d", &n, &targetPollution);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
scanf("%d", &pollution[i][j]);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
scanf("%d", &pollutionLimit[i][j]);
int left = 0, right = INF, answer = -1;
// 二分搜索最小天数
while (left <= right) {
int mid = (left + right) / 2;
if (calculatePollution(mid) <= targetPollution) {
right = mid - 1;
answer = mid;
} else {
left = mid + 1;
}
}
printf("%d\n", answer);
return 0;
}