本周主要学习了最短路的相关知识,28道题,大部分都有思路,在根据题解帮助排除误区后A了,一两道由于题目没懂没A。在解题过程中,更全面的了解了最短路径的四种算法。
一.例题解析
最短路径的题大多都是直接给出起点和终点,让我们根据所给数据组成最短路径。而根据题目的具体要求不同,一般用以下四种方法解答:
Dijstra算法:常用于单源最短路径的解决,是最常用的方法之一,但是,它虽然简单,却有一些弊端,因此在做题时,常常根据题目需求进行优化,常用的有堆优化等;(但是,有一个缺陷没法弥补,即无法计算有负权值的边)
例:
P1342 请柬 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1342题目要求:
这里的公交系统是非常特殊的:共有 nn 个站点和 mm 个线路,所有的线路都是单向的,连接两个站点。公共汽车离开起始点,到达目的地之后又空车返回起始点。
学生每天早上从总部所在的 11 号站点出发,乘公交车到一个预定的站点邀请乘客。每个站点都被安排了一名学生。在一天结束的时候,所有的学生都回到总部。现在需要知道的是,学生所需的公交费用的总和最小是多少。
分析:
这个是求权值的一道题,属于很常见的解题要求之一,实际上操作思想并不难,只要在进行最小路径寻找时,建立累加器加最短路径的权值即可。不过要记得在进行松弛操作时减去原来较长路径的权值,以防有误。(注意数据范围,一定要大,不要按习惯写int ,很容易越界)。
代码:
#include<bits/stdc++.h>
#include<iostream>
using namespace std;
const long long Maxn 2000065
#define next Next
long long read()
{
long long p=0,f=1;char c=getchar();
while((c<'0'||c>'9')&&(c!='-')) c=getchar();
if(c=='-') f=-1,c=getchar();
while(c>='0'&&c<='9') r=r*10+c-'0',c=getchar();
return p*f;
}
priority_queue< pair<ll,ll> >q;
long long ans,n,m,tot,ver[Maxn],head[Maxn],next[Maxn],edge[Maxn],d[Maxn],v[Maxn];
void add(long long x,long long y,long long z)
{
ver[++tot]=y;
next[tot]=head[x];
head[x]=tot;
edge[tot]=z;
}
void dij(long long s)
{
memset(d,0x7f,sizeof(d));
memset(v,0,sizeof(v));
d[s]=0;
q.push(make_pair(0,s));
while(q.size())
{
long long x=q.top().second;q.pop();
if(v[x]) continue;
v[x]=1;
for(int i=head[x];i;i=next[i])
{
ll y=ver[i],z=edge[i];
if(d[y]>d[x]+z)
{
d[y]=d[x]+z;
q.push(make_pair(-d[y],y));
}
}
}
}
int main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
{
long long x=read(),y=read(),z=read();
add(x,y,z);
}
dij(1);
for(int i=1;i<=n;i++) ans+=d[i];
for(int i=2;i<=n;i++)
{
dij(i);
ans+=d[1];
}
cout<<ans<<endl;
return 0;
}
Floyd算法:适用于多源最短路径的题目,常用于求长度的题。算法思想有些类似动态规划,但实现起来比较方便。
例:
P1339 [USACO09OCT]Heat Wave G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1339题目要求:
有一个 nn 个点 mm 条边的无向图,请求出从 ss 到 tt 的最短路长度。
分析:
这是最基本的最短路径题,就是所谓的模板题,这个题的条件是无向图,就是不断寻找当前的点与连接点的最小路径。
思路:
先把每个都储存为无穷大(可以用INF来建立,方便看懂),然后利用正常思路解题,但是注意,因为是无向图,要两遍松弛。(思路来自题解)
代码:
#include<bits/stdc++.h>
#include<iostream>
using namespace std;
const int MAXN = 6200 + 5;
const int INF = 2e9;
int u[MAXN], w[MAXN], v[MAXN], dis[2501], s, e, m, n;
bool t;
int main()
{
cin >> m >> n >> s >> e;
for (int i = 1; i <= n; i++)
cin >> u[i] >> v[i] >> w[i];
for (int i = 1; i <= m; i++)
dis[i] = INF;
dis[s] = 0;
for (int j = 1; j <= m-1; j++)
{
t = true;
for (int i = 1; i <= n; i++)//就是这,松弛操作两向
{
if (dis[v[i]] > dis[u[i]] + w[i])
{
dis[v[i]] = dis[u[i]] + w[i];
t = false;
}
if (dis[u[i]] > dis[v[i]] + w[i])
{
dis[u[i]] = dis[v[i]] + w[i];
t = false;
}
}
if (t)
break;
}
cout << dis[e];
}
Bellman ford算法:也是单源最短路径的主要使用方法,一般适用于有负权值的边的题;
SPFA算法:有非常强的适配性,可以很好的联合搜索,stl等方法进行解题,适用于单源多源等很多题目,广泛性高,解决能力强。
由于我多用floyd算法解题,对后面两种只是看懂会用,就不专门列例题解答。接下来主要谈谈心得。而且实际上,这很多题都是可以利用其他思路解决,比如在本周的题集中,我就看到了一些过往做过的题,其实,一些题利用不同的方法确实可以有不同的解题思路,明显的如并查集和搜索,但最短路径作为最小生成树的相交区域,也涉及到了二叉树的一些知识,而且有的题联系了一些未学过的知识,也就只能浅尝辄止。
二.解题心得
本周写题时,发现最短路径的有关题目同质化较高,都是在不同条件下用同样的方法解同样的题,然而,简单的确实一看就有思路,但难的确实很难下手,比如P6464 传送门 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题,我一开始的思路很简单,就是简单的dij去反复解,但看了题解才按照过程一步步明白了题目设置的坑,很可惜的是,最后只是过了案例,题目过不去,百思不得其解(最后改的案例都过不去了),这说明我的代码能力还有待提高。还有就是,有的题目依旧需要利用反向思维。对于没头绪的可以更深一步思考。
当然,本周收获也不能说是满打满的。对于SPFA算法,虽然绝大部分的题都有这种算法的解法,但我确实不是很熟练,所以在下周复习时要重点看一看。
本周博客确实不知应当如何具体去描述心路历程,很多想法都已经变成题解放在excle里了,而且本周主要就是看题,导致本周的博客可能有些寡淡,这周就先这样吧。