第一行照例留给本鸽子精咕咕咕。
以下为正文:
首先:所有无负环单纯求最短路,优先用floyd或dijkstra!spfa太容易tle了,根据数据范围理智选择!
其次:两种算法都可拓展为求最长路,初始化dis为0,每次判断大于即更新。或者判断是否存在负环,如果n-1次后还可以进行dis往小的方向更新,即可说明存在负环。或者是求正环,也是跑最长路,n-1后还可以往大的方向更新即可说明有正环。
一、Bellman-Ford
1.bellman-ford可用于有负权值的图来计算最短路。
2.算法思想是给顺序输入的每条边进行标号,利用每条边来进行松弛操作,总共形成n-1条边(一共有n个点)。
3.时间复杂度为O(nm),n为点的个数,m为边数。
核心即为下面的代码(注释在代码里(就几行(主要是我没找到合适的题目
int u[205],v[205],w[205];//u,v,w分别存的是第i条边的起始点指向点和权值
int dis[205];//到当前点的最短路
memset(dis,inf,sizeof(dis));//初始化为inf
dis[1] = 0;//令起始点的距离为0
for(int i = 0; i < n-1; ++i)
{//组成n-1条边
for(int j = 1; j <= m; ++j)//利用每条边进行更新
if(dis[v[j]] > dis[u[j]]+w[j])
dis[v[j]] = dis[u[j]]+w[j];
}
二、SPFA
1.spfa在Bellman-Ford算法的基础上,利用队列来进行时间优化的一种可用于解决负边权判断负环的最短路算法。
2.spfa实际上就是先将起始点放入队列中,每次取出一个点,利用这个点将其周围的(与其相连的)点进行优化,如果某点有过松弛,就入队,重复这样的过程直到队列为空。但需要记录进队次数,如果一个点入队次数多于n次,即说明存在负环,则返回负环的结果。
3.多用链式前向星存图。不会指路博客:图的存储与遍历
4.判断是否存在负环时,只需要加一个数组time,来表示每个点入队的次数,当大于n时,执行关于负环的操作即可。
5.spfa时间复杂度一般为O(km),k为一个常数,m为边的个数(当然可以创造出卡spfa的数据使其变为O(nm)),所以没有负权值的情况下优先考虑其他最短路算法。
以下代码以洛谷P1744为例(四月做的题我现在才来写博客,果然是一个职业鸽手呢(乖巧.jpg))
//spfa
#include <cstdio>
#include <string>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <map>
#include <cmath>
#define inf 0x3f3f3f3f
using namespace std;
int cnt = 1;
double dis[10005];
bool vis[10005];
int x[105],y[105];
int n;
double w[105][105];
void init()
{
for(int i = 1; i < 105; ++i)
for(int j = 1; j < 105; ++j)
w[i][j] = inf;
for(int i = 1; i < 105; ++i)
{
dis[i] = inf;//初始化所有起点到该点的值为inf
vis[i] = 0;
}
}
void ad(int u,int v)
{
double a;
long long b = (x[v]-x[u])*(x[v]-x[u])+(y[v]-y[u])*(y[v]-y[u]);//权值计算
a = sqrt(b);
w[u][v] = a;
w[v][u] = a;
}
void spfa(int s)
{
queue<int>q;
q.push(s);
dis[s] = 0;
vis[s] = 1;
while(!q.empty())
{
int now = q.front();
q.pop();
vis[now] = 0;
for(int i = 1; i <= n; ++i)
{
if(dis[i]>w[now][i]+dis[now])
{
dis[i] = w[now][i]+dis[now];
if(!vis[i])
{
q.push(i);
vis[i] = 1;
}
}
}
}
}
int main()
{
int f,t,m,s,e;
init();//初始化
scanf("%d",&n);
for(int i = 1; i <= n; ++i)
{
scanf("%d%d",&x[i],&y[i]);
}//记录每家店的坐标用来计算路径
scanf("%d",&m);
for(int i = 0; i < m; ++i)
{
scanf("%d%d",&f,&t);
ad(f,t);
}
scanf("%d%d",&s,&e);
spfa(s);
printf("%.2f\n",dis[e]);
return 0;
}
后续应该还会更练习题的qwq
upd:
EOIymp1453 bellman-ford模板题
洛谷P3385 这道题我用bellman-ford判断的负环,要注意权值大于等于0的时候是双向边,要注意是从1起点的能到达的负环。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,m;
int u[6005],v[6005],w[6005];
int dis[3005];
bool flag = 1;
int tt = 0;
memset(dis,inf,sizeof(dis));
dis[1] = 0;
scanf("%d%d",&n,&m);
for(int i = 1; i <= m; ++i)
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
if(u[i] == 1||(w[i] >= 0 && v[i] == 1))
flag = 0;
if(w[i] >= 0)
{
tt++;
u[m+tt] = v[i];
v[m+tt] = u[i];
w[m+tt] = w[i];
}
}
if(flag == 1)
{
printf("N0\n");
continue;
}
for(int i = 0; i < n-1; ++i)
{
for(int j = 1; j <= m+tt; ++j)
if(dis[v[j]] > dis[u[j]]+w[j])
dis[v[j]] = dis[u[j]]+w[j];
}
for(int j = 1; j <= m+tt; ++j)
{
if(dis[v[j]]>dis[u[j]]+w[j])
flag = 1;
}
if(flag == 1)
printf("YE5\n");
else
printf("N0\n");
}
return 0;
}
洛谷P2136 这道题我用的spfa求最短路(用链式前向星存图),注意拉近距离可以从起点拉近也可以从终点(这就是我第一提交90分的原因),跑两次分别从起始点跑spfa,比较出min来,注意如果负环时的输出。
好了最短路就告一段落了qwq虽然现在会了不等于未来还会qwq
欢迎指出错误qwq