邮递员送信
题目描述
有一个邮递员要送东西,邮局在节点 1 1 1。他总共要送 n − 1 n-1 n−1 样东西,其目的地分别是节点 2 2 2 到节点 n n n。由于这个城市的交通比较繁忙,因此所有的道路都是单行的,共有 m m m 条道路。这个邮递员每次只能带一样东西,并且运送每件物品过后必须返回邮局。求送完这 n − 1 n-1 n−1 样东西并且最终回到邮局最少需要的时间。
输入格式
第一行包括两个整数, n n n 和 m m m,表示城市的节点数量和道路数量。
第二行到第 ( m + 1 ) (m+1) (m+1) 行,每行三个整数, u , v , w u,v,w u,v,w,表示从 u u u 到 v v v 有一条通过时间为 w w w 的道路。
输出格式
输出仅一行,包含一个整数,为最少需要的时间。
样例 #1
样例输入 #1
5 10
2 3 5
1 5 5
3 5 6
1 2 8
1 3 8
5 3 4
4 1 8
4 5 3
3 5 6
5 4 2
样例输出 #1
83
提示
对于 30 % 30\% 30% 的数据, 1 ≤ n ≤ 200 1 \leq n \leq 200 1≤n≤200。
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 3 1 \leq n \leq 10^3 1≤n≤103, 1 ≤ m ≤ 1 0 5 1 \leq m \leq 10^5 1≤m≤105, 1 ≤ u , v ≤ n 1\leq u,v \leq n 1≤u,v≤n, 1 ≤ w ≤ 1 0 4 1 \leq w \leq 10^4 1≤w≤104,输入保证任意两点都能互相到达。
思路分析
看到这道题,起初没有很好的想法,暴力+dijkstra堆优化,可以过几个点但是不能AC,摆出一开始的代码
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 2e3 + 10;
const int M = 2e5 + 10;
typedef pair<int, int> PII;
int e[M], ne[M], h[M], w[M], idx;
bool st[N][N];
int dis[N][N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dijkstra(int x)
{ //得到x到其他点的最短距离
priority_queue<PII, vector<PII>, greater<PII>> heap;
dis[x][x] = 0;
heap.push({0, x});
while (heap.size())
{
PII t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if(st[x][ver])continue;
st[x][ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dis[x][j] > distance + w[i])
{
dis[x][j] = distance + w[i];
heap.push({dis[x][j], j});
}
}
}
}
int main()
{
memset(h, -1, sizeof h);
int n, m;
cin >> n >> m;
memset(dis, 0x3f, sizeof dis);
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
for (int i = 1; i <= n; i++)
{
dijkstra(i);
}
long long res = 0;
for (int i = 2; i <= n; i++)
{
res += dis[1][i] + dis[i][1];
}
cout << res << endl;
return 0;
}
后来看了题解,发现题目妙就妙在可以反向建图,详情看代码
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 2e3 + 10;
const int M = 2e5 + 10;
typedef pair<int, int> PII;
int e[M], ne[M], h[M], w[M], idx;
bool st[N];
int dis[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void dijkstra(int x)
{
memset(dis, 0x3f, sizeof(dis)); //初始化dis数组
priority_queue<PII, vector<PII>, greater<PII>> heap; //初始化小根堆
dis[x] = 0; //记录出发点距离为0
heap.push({0, x}); //进入优先队列
while (heap.size())
{ //进入循环
PII t = heap.top(); //记录队头元素
heap.pop(); //队头元素出队
int ver = t.second, distance = t.first;
if (st[ver])
continue; //如果该点已经遍历过了,那么我们跳出本次循环
st[ver] = true; //记录该点已经遍历
for (int i = h[ver]; i != -1; i = ne[i])
{ //遍历该点能到达的点
int j = e[i]; // j 是i能到达的点
if (dis[j] > distance + w[i])
{ //更新距离
dis[j] = distance + w[i];
heap.push({dis[j], j});
}
}
}
}
int main()
{
memset(h, -1, sizeof h);
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
add(b + n, a + n, c);
}
long long res = 0;
dijkstra(1); //正着跑一次
for (int i = 2; i <= n; i++)
res += dis[i];
dijkstra(1 + n); //反着跑一次
//思路就在这里,如果暴力的话,这里要跑n次,复杂度上和flody差不多
//反向建图的魅力就在于此,本来是从n到1的距离,反向建图以后就变成了1到n的距离,实在是妙哉!
for (int i = 2 + n; i <= n << 1; i++)
res += dis[i];
cout << res << endl;
return 0;
}
同样的把dijkstra换成spfa也可以过
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 210;
const int M = 210 * 210;
bool st[N];
int build[N];
int dis[N], e[M], ne[M], w[M], h[N], t[M], idx;
int add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, t[idx] = max(build[a], build[b]), ne[idx] = h[a], h[a] = idx++;
} // t[idx]表示该边在什么时刻有效
int spfa(int s, int d, int ti) //求ti时刻,s到d的距离
{
memset(st, false, sizeof st);
memset(dis, 0x3f, sizeof dis);
dis[s] = 0;
queue<int> q;
q.push(s);
st[s] = true;
while (q.size())
{
int ver = q.front();
q.pop();
st[ver] = false;
for (int i = h[ver]; i != -1; i = ne[i])
{
if (t[i] > ti)
{
continue;
} //说明此时此路还没建好
int j = e[i];
if (dis[j] > dis[ver] + w[i])
{
dis[j] = dis[ver] + w[i];
if (!st[j])
{
st[j] = true;
q.push(j);
}
}
}
}
if (dis[d] == 0x3f3f3f3f)
return -1;
else
return dis[d];
}
int main()
{
memset(h, -1, sizeof h);
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++)
scanf("%d", &build[i]);
for (int i = 0; i < m; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
add(b, a, c);
}
int q;
scanf("%d", &q);
for (int i = 0; i < q; i++)
{
int x, y, t;
scanf("%d%d%d", &x, &y, &t);
cout << spfa(x, y, t) << endl;
}
return 0;
}