\(AcWing\) \(345\) 牛站
一、题目描述
给定一张由 \(T\) 条边构成的无向图,点的编号为 \(1\)∼\(1000\) 之间的整数。
求从起点 \(S\) 到终点 \(E\) 恰好 经过 \(N\) 条边(可以重复经过)的 最短路。
注意: 数据保证一定有解
输入格式
第 \(1\) 行:包含四个整数 \(N\),\(T\),\(S\),\(E\)。
第 \(2..T+1\) 行:每行包含三个整数,描述一条边的边长以及构成边的两个点的编号。
输出格式
输出一个整数,表示最短路的长度
二、前导知识
- 图中任意两点间路径数量
VUA~125 Numbering Paths- 图中两点路径为\(k\)的方案数
P4159 [SCOI2009] 迷路 这道题放这不太合适,因为还有拆点,有点难度~
三、题目解析
本题并不是让我们求路径的条数,而是求 在路径条数限定的情况下,求最短路径长度
设\(g\)是图的邻接矩阵,\(t[i][j]\)表示从\(i\)恰好经过两条边到达\(j\)的 最短路径长度 ,则
\[\large t[i][j] = min(g[i][k] + g[k][j])
\]
可以想象一下,\(i\),\(j\)两个点,如果之间有两条边,则需要引入一个点才行,设为\(k\),则路径长度就是\(g[i][k]+g[k][j]\),而\(k\)的范围是$ \in [1,n]$,代码:
for(int k = 1;k <= n;k++)
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
t[i][j] = min(t[i][j],g[i][k] + g[k][j]);
这被称作:广义矩阵乘法
快速幂对广义矩阵乘法的优化
广义矩阵乘法可以求\(i\)经过 两条边 到达\(j\)的 最短路径 长度,则对\(g\)做\(k\)次 自乘 就可以求经过\(k\)条边的 最短路径 长度了。
void mul(int a[][N],int b[][N]){
int t[N][N];
memset(t,0x3f,sizeof t); //预求最小,先设最大
for(int k = 1;k <= n;k++)
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
t[i][j] = min(t[i][j],a[i][k] + b[k][j]);
// sizeof 不能在此处使用,因为a数组其实是指针传入,无法获取大小
// 有两个办法可以解决:
// (1)采用全局的sizeof t进行替代 (简单)
// (2)在函数中增加大小这样一个参数,由调用者赋值传入 (麻烦)
memcpy(a,t,sizeof t);
}
void qmi(){
memcpy(f,a,sizeof f);
k--;
while(k){
if(k & 1) mul(f,g);
mul(g,g);
k >>= 1;
}
}
离散化
值得注意的是点的编号最大是1000
,最多只有100
条边,也就是最多200
个节点,所以可以对节点编号做下 离散化,较为简单,参考代码即可。
小坑一枚
还有个要注意的地方时,本题读取边的信息的时候是先读取边长,再读取两个节点的编号,一般题目都是最后读取边权,要注意不要写错数据的读取代码。
不作离散化会怎么样?
通过了 \(5/12\)个数据 结果: \(Time\) \(Limit\) \(Exceeded\), 无法\(AC\)
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int s, e;
int n, m;
int k;
int g[N][N], f[N][N];
int t[N][N];
void mul(int a[][N], int b[][N]) {
memset(t, 0x3f, sizeof t);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
for (int k = 1; k <= n; k++)
t[i][j] = min(t[i][j], a[i][k] + b[k][j]);
memcpy(a, t, sizeof t);
}
void qmi() {
memcpy(f, g, sizeof f);
k--;
while (k) {
if (k & 1) mul(f, g);
mul(g, g);
k >>= 1;
}
}
int main() {
scanf("%d %d %d %d", &k, &m, &s, &e);
memset(g, 0x3f, sizeof g); //地图初始化
while (m--) {
int a, b, c;
scanf("%d %d %d", &c, &a, &b);
n = max({n, a, b});
g[a][b] = g[b][a] = min(g[a][b], c); //无向图
}
//矩阵快速幂
qmi();
//输出
printf("%d\n", f[s][e]);
return 0;
}
使用\(id\)数组做离散化
#include <bits/stdc++.h>
using namespace std;
const int N = 205;
int id[1010];
int s, e;
int n, m;
int k;
int g[N][N], f[N][N];
int t[N][N];
void mul(int a[][N], int b[][N]) {
memset(t, 0x3f, sizeof t);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
for (int k = 1; k <= n; k++)
t[i][j] = min(t[i][j], a[i][k] + b[k][j]);
memcpy(a, t, sizeof t);
}
void qmi() {
// 写法1:使用一个空的矩阵,需要执行k次幂
// memset(f, 0x3f, sizeof f);
// for (int i = 1; i <= n; i++) f[i][i] = 0;
//写法2,使用一个拷贝矩阵,需要执行k-1次幂
memcpy(f, g, sizeof f);
k--;
while (k) {
if (k & 1) mul(f, g);
mul(g, g);
k >>= 1;
}
}
int main() {
scanf("%d %d %d %d", &k, &m, &s, &e);
//离散化
id[s] = ++n; //起点编号
id[e] = ++n; //终点编号
s = id[s], e = id[e]; //新起点,新征程
memset(g, 0x3f, sizeof g); //地图初始化
while (m--) {
int a, b, c;
scanf("%d %d %d", &c, &a, &b);
if (!id[a]) id[a] = ++n; // a节点离散化
if (!id[b]) id[b] = ++n; // b节点离散化
a = id[a], b = id[b];
g[a][b] = g[b][a] = min(g[a][b], c); //无向图
}
//矩阵快速幂
qmi();
//输出
printf("%d\n", f[s][e]);
return 0;
}
使用\(STL+unordered\_map\)进行离散化
#include <bits/stdc++.h>
using namespace std;
const int N = 205;
unordered_map<int, int> id;
int s, e; //起点 终点
int n, m; //离散化后的节点号 m条边
int k; //恰好k条边
int g[N][N]; //图
int f[N][N]; //最短距离
//矩阵乘法
void mul(int a[][N], int b[][N]) {
int t[N][N];
memset(t, 0x3f, sizeof t);
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
//求指定边数情况下的最短距离
//广义上的矩阵乘法
t[i][j] = min(t[i][j], a[i][k] + b[k][j]);
memcpy(a, t, sizeof t);
}
//快速幂
void qmi() {
// 写法1:使用一个空的矩阵,需要执行k次幂
// memset(f, 0x3f, sizeof f);
// for (int i = 1; i <= n; i++) f[i][i] = 0;
//写法2,使用一个拷贝矩阵,需要执行k-1次幂
memcpy(f, g, sizeof f);
k--;
while (k) {
if (k & 1) mul(f, g); //矩阵快速幂
mul(g, g);
k >>= 1;
}
}
// AC 481 ms
// 这个速度真是很牛X
int main() {
scanf("%d %d %d %d", &k, &m, &s, &e);
//起点的离散化后号为1
id[s] = ++n;
//如果e与s不同,那么e的新号为2,否则为1
if (!id.count(e)) id[e] = ++n;
//重新对s和e给定新的号码
s = id[s], e = id[e];
//初始化邻接矩阵
memset(g, 0x3f, sizeof g);
while (m--) {
int a, b, c;
scanf("%d %d %d", &c, &a, &b);
if (!id.count(a)) id[a] = ++n; //记录点的映射关系a-> id[a]
if (!id.count(b)) id[b] = ++n; //记录点的映射关系b-> id[b]
a = id[a], b = id[b]; //对a,b给定新的号码
//利用新的号码将边长c记录到邻接矩阵中
g[a][b] = g[b][a] = min(g[a][b], c);
}
//快速幂+动态规划思想
qmi();
//输出从起点到终点,恰好经过k条边的最短路径
printf("%d\n", f[s][e]);
return 0;
}