Dijkstra
O ( ( n + m ) l o g n ) O((n+m)logn) O((n+m)logn)
只能用于正权图
#include<bits/stdc++.h>
using namespace std;
int n,m,st,ed,dis[2505];
bool vis[2505];
struct edge{
int v,w;
edge(){
}
edge(int V,int W) {
v=V,w=W;}
};
void read(int &x) {
int f=1;x=0;char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') {
x=(x<<1)+(x<<3)+(c^48);c=getchar();}
x*=f;
}
priority_queue<pair<int,int> > q;
vector<edge> v[2505];
void dijkstra() {
q.push(make_pair(0,st));
dis[st]=0;
while(q.size()) {
int x=q.top().second;q.pop();
if(vis[x]) continue;
vis[x]=1;
for(int i=0;i<v[x].size();i++) {
int vi=v[x][i].v,wi=v[x][i].w;
if(dis[x]+wi<dis[vi]) {
//松弛操作
dis[vi]=dis[x]+wi;
q.push(make_pair(-dis[vi],vi));
}
}
}
}
int main() {
memset(dis,0x3f,sizeof(dis));
cin>>n>>m>>st>>ed;
for(int i=1;i<=m;i++) {
int x,y,z;
cin>>x>>y>>z;
v[x].push_back(edge(y,z));
v[y].push_back(edge(x,z));
}
dijkstra();
printf("%d",dis[ed]);
}
Bellman-Ford
O ( n m ) O(nm) O(nm)
如果n-1次松弛后仍然不满足三角形不等式,则说明有负环(这个环内每次都可以更新,因为一次比一次小),否则就没有负环(可以收敛)。
注意:有负环时bellman算法已经崩溃了,不能作为任何答案,所以虽然在负权下可以工作,但容易RE
判环+输出路径
#include<bits/stdc++.h>
using namespace std;
int n,m,st,ed,dis[505],num[505],pre[505];
struct Edge{
int u,v,w;
}e[20005];
void print(int x) {
if(!pre[x]) {
printf("%d ",x);
return;
}
print(pre[x]);
printf("%d ",x);
}
bool bellman_ford(int S) {
dis[S]=0;
num[S]=0;
pre[S]=0;
for(int i=1;i<n;i++) {
for(int j=1;j<=m;j++) {
int u=e[j].u,v=e[j].v,w=e[j].w;
if(dis[v]>dis[u]+w||(dis[v]==dis[u]+w&&num[v]>num[u]+1)||(dis[v]==dis[u]+w&&num[v]==num[u]+1&&u<pre[v])) {
dis[v]=dis[u]+w;
num[v]=num[u]+1;
pre[v]=u;
}
}
}
for(int j=1;j<=m;j++) {
int u=e[j].u,v=e[j].v,w=e[j].w;
if(dis[v]>dis[u]+w) {
return 0;
}
}
return 1;
}
int main() {
memset(dis,0x3f,sizeof(dis));
memset(num,0x3f,sizeof(num));
memset(pre,0x3f,sizeof(pre));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) {
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
}
scanf("%d%d",&st,&ed);
if(bellman_ford(st)) {
printf("%d\n",dis[ed]);
print(ed);
}
else printf("No Solution");
}
Spfa
O ( k n ) O(kn) O(kn),但容易被卡(尤其是稠密图)
基于队列优化的bellman算法,负权图同样可以工作
可以用 n u m [ ] num[] num[]数组记录路径长度,只要有松弛操作就令 n u m [ v ] = n u m [ u ] + 1 num[v]=num[u]+1 num[v]=num[u]+1,若 n u m [ v ] > n num[v]>n num[v]>n则说明有负环
#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
int n,m,dis[maxn];
bool vis[maxn];
void read(int &x) {
int f=1;x=0;char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') {
x=(x<<1)+(x<<3)+(c^48);c=getchar();}
x*=f;
}
struct edge{
int v,w;
edge(){
}
edge(int V,int W) {
v=V,w=W;
}
};
queue<int> q;
vector<edge> v[maxn];
void spfa() {
q.push(1);
dis[1]=0;
vis[1]=1;
while(q.size()) {
int x=q.front();q.pop();
vis[x]=0;
for(int i=0;i<v[x].size();i++) {
int vi=v[x][i].v,wi=v[x][i].w;
if(dis[x]+wi<dis[vi]) {
dis[vi]=dis[x]+wi;
if(!vis[vi]) {
q.push(vi);
vis[vi]=1;
}
}
}
}
}
int main() {
memset(dis,0x3f,sizeof(dis));
read(n),read(m);
for(int i=1;i<=m;i++) {
int x,y,z;
read(x),read(y),read(z);
v[x].push_back(edge(y,z));
v[y].push_back(edge(x,z));
}
spfa();
printf("%d",dis[n]);
}
最小路径生成树
eg.黑暗城堡
解析:对于1到 i i i的最短路,事实上存在 j j j使 d i s [ i ] = d i s [ j ] + G ( j , i ) dis[i]=dis[j]+G(j,i) dis[i]=dis[j]+G(j,i)
这给我们启发: j j j的最短路与 i i i的最短路存在联系
如果把每一个 i i i与前继 j j j连起来,可以发现一定找得到这个 j j j,那么 i i i的最短路一定由 j j j贡献,就只需要考虑 j j j的最短路了。
题目又告诉我们这是一个树形结构,且没有负边,边是n-1,刚好n个点,所以每个点 i i i只能找一个前继点 j j j,我们只需统计num[i]表示满足条件的j的个数,然后把num[1~n]相乘即可。
至于求dis[i],可以用dijkstra预处理完成。