Preface
暑假集训用到了最短路的板子但之前的板子没存下来,在加上之前也看的不是很明白,故这里放点笔记;
部分板子目前还未测试过,谨慎使用(2023.7.15);
Content
Problem
用链式前向星方式存储一个有权图G(V:E),找到点 i,j 之间权值最小的路径长度;
注意:有时题目中的图其实就是树,而树的路径长度用 dfs 就好了不需要用这个;
通常包括Dijstra 算法,SPFA 和 Floyd算法,其中小心SPFA被卡爆();
Solution
Dijstra 算法
Notice:
-
该算法只能处理边正权图的单源最短路问题,但是它的效率十分优秀,一般采用带小根堆优先队列优化~~(ZKW线段树!Fibonnaci堆!)~~;
-
时间复杂度为O((|V|+|E|)log|V|);
-
但这个复杂度放在稠密图里可能会被hack,需要换成邻接矩阵的方式存图,复杂度为O(|V|^2),遇到了再补吧;
Proof:
- 对于每个点v均维护一个「当前最短距离数组」dis[v] 以及「访问数组」vis[v] ,首先将dis[u]初始化为0,将其他点的距离初始化为无穷,并将所有点初始化为未访问的。记e[i]:u-v的边权为 e[i].w。然后进行以下步骤:
- 从所有未访问的点中,找出当前距离最小的,并将其标记为已访问的;
- 调整所有出边连接但尚未访问的点,转移状态;
- 重复1和2步骤,直到所有点都已访问,因为所有边权权值均为正的,所以用之后访问的结点是不能更新已经访问过的结点的(因为贪婪算法下若可以更新的话必定在还在之前就已经更新过了);
- 可以用
pre[y]=i
维护最短的路径(点边关系);
Template:
标准版询问单源最短路径
#define local
#include<bits/stdc++.h>
#include<iostream>
using namespace std;
const int N=200005;
struct Edge{
int head;
int to;
int nxt;
int w;
}e[N];
void add(int u,int v,int w,int& cnt,std::vector<int>& head){
e[cnt].nxt=head[u];
head[u]=cnt;
e[cnt].head=u,e[cnt].to=v,e[cnt].w=w;
cnt++;
return ;
}
long long dis[maxn];
const int INF=1000000009;
class Node{public:
long long distance;
int position;
Node(long long a,int b){distance=a;position=b;}
bool operator >(const Node&obj) const{
return distance>obj.distance;
};
};
class cmp{public:
bool operator()(Node&a,Node&b)const{
return a>b;
}
};
std::priority_queue<Node,std::vector<Node>,cmp> disQ;
void Dijstra(int u,int n,long long (&dis)[maxn],std::vector<int>& head){
for(int i=1;i<=n;i++) dis[i]=INF;
dis[u]=0;
disQ.push(Node(0,u)); bool vis[maxn];
memset(vis,false,sizeof(vis));
while(!disQ.empty()){
int x=disQ.top().position;
disQ.pop();
if(vis[x]) continue; vis[x]=true; for(int j=head[x];j!=-1;j=e[j].nxt){
int y=e[j].to; if(dis[y]>dis[x]+e[j].w){
dis[y]=dis[x]+e[j].w;
if(!vis[y]) disQ.push(Node(dis[y],y));
}
}
}
return ;
}
int main ()
{
#ifdef local
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int n,m,s;
std::cin>>n>>m>>s;
int cnt=0;
std::vector<int> head; head.resize(n+1,-1);
for(int i=0;i<m;i++){
int u,v,w;
std::cin>>u>>v>>w;
add(u,v,w,cnt,head);
}
Dijstra(s,n,dis,head);
for(int i=1;i<=n;i++){
if(i!=1) printf(" ");
std::cout<<dis[i];
}
return 0;
}
Floyd 算法
Notice
- 算法是基于最朴素的动态规划的思想,求出全局上的任意两点的最短路问题,可以处理负边权的情况;
- 我们知道对于图G 中存在负权值环的话,沿着这个环一直前进是不会得到最短路的, 算法也无法处理这类的问题;
- 在一些加了若干限制的最短路问题中,往往都要回归到朴素的算法来;
Proof
考虑对每个值,「到 的路径只允许经过标号不大于的点」中最短路径长度,明显是我们所求问题的答案;
Template:
弱化版单源最短路径,不是正解,只有70分
#include<bits/stdc++.h>
#include<iostream>
const int maxn=10005;
const int N=500005;
long long dis[maxn][maxn];
const long long INF= (1<<31)-1;
void Floyd(int n,long long (&dis)[maxn][maxn]){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=std::min(dis[i][j],dis[i][k]+dis[k][j]);
}
}
}
return ;
}
int main ()
{
#ifdef local
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int n,m,s;
std::cin>>n>>m>>s;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]= INF;
}
}
for(int i=0;i<m;i++){
int u,v,w;
std::cin>>u>>v>>w;
dis[u][v]=std::min(dis[u][v],w*1LL); }
Floyd(n,dis);
for(int i=1;i<=n;i++){
if(i!=1) printf(" ");
std::cout<<((i!=s)?dis[s][i]:0);
}
return 0;
}
SPFA 算法
Notice:
- 对于一般的带权图,不同于正权图的操作每次取出最小的来进行下一步,边权有负数是不保证正确性的,所以我们维护一个普通的队列就行了,这也是经常得卡爆的地方,构造数据可以欺骗算法进入冗余的分支,使得队列的进出和多了很多不必要的操作;
- 这个算法本质是算法的局部优化,复杂度为;
- 所以说没必要仔细解释算法对全局的次操作了,卡的一定卡;
- (保证输入是随机数据)慎用,不要偷懒!
因为它已经死了;
Template:
#include<bits/stdc++.h>
#include<iostream>
const int maxn=100005;
const int N=500005;
struct Edge{
int head;
int to;
int nxt;
int w;
}e[N];
void add(int u,int v,int w,int& cnt,std::vector<int>& head){
e[cnt].nxt=head[u];
head[u]=cnt;
e[cnt].head=u,e[cnt].to=v,e[cnt].w=w;
cnt++;
return ;
}
const long long INF=(1<<31)-1;
long long dis[maxn];
void SPFA(int s,int n,long long (&dis)[maxn],std::vector<int> &head){
for(int i=1;i<=n;i++) dis[i]=INF;
dis[s]=0;
bool vis[maxn];
memset(vis,false,sizeof(vis));
std::queue<int> disQ;
disQ.push(s);
while(!disQ.empty()){
int x=disQ.front();
disQ.pop();
vis[x]=false;
for(int i=head[x];i!=-1;i=e[i].nxt){
int y=e[i].to;
if(dis[y]>dis[x]+e[i].w){
dis[y]=dis[x]+e[i].w;
if(!vis[y]){
vis[y]=true;
disQ.push(y);
}
}
}
}
}
int main ()
{
#ifdef local
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int n,m,s;
std::cin>>n>>m>>s;
int cnt=0;
std::vector<int> head; head.resize(n+1,-1);
for(int i=0;i<m;i++){
int u,v,w;
std::cin>>u>>v>>w;
add(u,v,w,cnt,head);
}
SPFA(s,n,dis,head);
for(int i=1;i<=n;i++){
if(i!=1) printf(" ");
std::cout<<dis[i];
}
return 0;
}
Example
暂时没有;
洛谷P3385 【模板】负环
洛谷P5909【模板】Johnson 全源最短路
已寄,这篇就当笔记吧;