先附一段模板代码,来源洛谷大佬:Ophelia
#include<bits/stdc++.h>
#include<cmath>
using namespace std;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;//STL优先队列(使用pair的情况下)的定义方法。由于要采用小根堆,所以需要这样定义。注*
int fir[500001],to[500001],val[500001],nex[500001],n,m,s,u,v,w,cnt;
const int inf=2147483647;
long long dis[10001];//为了避免判定相加时爆掉int成负数,此处采用long long。注1*
bool book[100001];
void add_edge(int a,int b,int c)//链式前向星存图,不懂的可以问度娘
{
to[++cnt]=b;
val[cnt]=c;
nex[cnt]=fir[a];
fir[a]=cnt;
}
int main()
{
cin>>n>>m>>s;
for(int i=1; i<=m; i++)
{
cin>>u>>v>>w;
add_edge(u,v,w);
}
for(int i=1; i<=n; i++)
dis[i]=inf;
dis[s]=0;//初始化dis数组
q.push(make_pair(0,s));//这里采用C++自带二元组编写。注2*
while(q.size())//当堆中还有元素
{
int x=q.top().second;//q.top()取出堆顶,也就是当前距离起点最近的点。
q.pop();//取完就删
if(book[x]) continue;//如果这个定点标记过了,就不用。注3*
book[x]=1;//标记一下
for(int i=fir[x]; i; i=nex[i])
{
if(dis[to[i]]>dis[x]+val[i])//dijkstra算法核心语句
{
dis[to[i]]=dis[x]+val[i];
q.push(make_pair(dis[to[i]],to[i]));//每次松弛成功,把关于当前点的信息压入堆。
}
}
}
for(int i=1;i<=n;i++)
cout<<dis[i]<<" ";
return 0;
}
/*
注:priority_queue<(数据类型)>的定义方法默认是大根堆。只有priority_queue<(数据类型),vector<(数据类型)>,greater<(数据类型)> >的方法才能定义小根堆。另外,应该用空格分开两个连在一起的尖括号(<<或>>),否则编译器会把它识别成流读取运算符。
当然,你也可以选择重载运算符,不过我觉得这样更好些。毕竟不少人不会重载。
另外,优先队列虽然等价于堆,但是内部原理是通过给予变量一个优先值来实现的,与堆不同。
注1:C++的计算过程中的数据是以两个变量中最高的数据类型存储的,与结果保存变量无关。两个int相加保存在一个long long变量里,如果结果超出int,还是会炸的。
注2:C++自带的二元组pair,定义方法是pair<(数据类型1),(数据类型2)>(名称)。相当于一个包含两个变量的结构体。pair中的两个成员使用.first和.second进行访问。在优先队列里,pair是以first为第一关键字、以second为第二关键字排序的。向pair中插入元素可以直接像结构体一样赋值,也可以使用make_pair()插入。
注3:这里采用的是懒惰删除的思想。因为STL的优先队列不支持对内部元素的随机删除,所以采用一个标记。如果曾经使用过,那么在它到堆顶的时候就不使用它。相当于把删除操作延迟到堆顶进行。
*/
附一题:
题目: 有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径。
输入:
第一行为整数T,表示有T个case(测试实例)。
接下来每个case包含: 第1行给出4个正整数N、M、S、D,其中N(2<=N<=500)是城市的个数,顺便假设城市的编号为0~(N-1);M是高速公路的条数;S是出发地的城市编号;D是目的地的城市编号。随后的M行中,每行给出一条高速公路的信息,分别是:城市1、城市2、高速公路长度、收费站,中间用空格分开,数字均为整数且不超过500。输入保证解的存在。
输出:
在一行里输出路径的长度和收费总额,数字间以空格分隔,输出结尾不能有多余空格。
先上代码:
#include <bits/stdc++.h>
using namespace std;
#define MAX 1003
#define INF 2147483647
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> q;
int first[MAX],cost[MAX],cnt=0,n,m,s,d;
long long dis[MAX];
bool vis[MAX];
struct edge
{
int to;
int w;
int c;
int next;
}edges[2600000];
void add(int u,int v,int w,int c)
{
edges[++cnt].to=v;
edges[cnt].w=w;
edges[cnt].c=c;
edges[cnt].next=first[u];
first[u]=cnt;
}
void Dijkstra()
{
dis[s]=0;
cost[s]=0;
q.push(make_pair(0,s));
while(!q.empty())
{
int x=q.top().second;
q.pop();
if(vis[x]) continue;
vis[x]=1;
for(int i=first[x];~i;i=edges[i].next)
{
if(dis[edges[i].to]>dis[x]+edges[i].w)
{
dis[edges[i].to]=dis[x]+edges[i].w;
cost[edges[i].to]=cost[x]+edges[i].c;
q.push(make_pair(dis[edges[i].to],edges[i].to));
}
else if(dis[edges[i].to]==dis[x]+edges[i].w&&cost[edges[i].to]>cost[x]+edges[i].c)
{
cost[edges[i].to]=cost[x]+edges[i].c;
q.push(make_pair(dis[edges[i].to],edges[i].to));
}
}
}
cout<<dis[d]<<" "<<cost[d]<<endl;
}
int main()
{
int t;
cin>>t;
while(t--)
{
while(!q.empty()) q.pop();
cnt=0;
memset(first,-1,sizeof(first));
memset(vis,0,sizeof(vis));
cin>>n>>m>>s>>d;
for(int i=1;i<=m;i++)
{
int a,b,c,e;
cin>>a>>b>>c>>e;
add(a,b,c,e);
add(b,a,c,e);
}
for(int i=0;i<n;i++)
{
dis[i]=INF;
cost[i]=INF;
}
Dijkstra();
}
return 0;
}
注:本题与模板相比增加了一组权值,共有两组权值:路程和花费。
根据题目要求,求最短路径时先比较路程,路程相同再比较花费,选择花费较小的。
笔记:
1、
first数组用于存储以某一顶点为起点的最后一条半的边的序号。例如first[0]的值是以0为起点的最后一条边的序号。
在模板题中,顶点从1开始,初始化first数组时,将first数组初始化为0,当遍历到以0为起点时退出。
但在该题中,顶点从0开始,所以初始化为-1,保证以0为起点的边可以正常进行运算,当遍历到以-1为起点的边时退出。
2、
dis数组与cost数组用于存储权值,初始化为INF保证正确运算。
3、
优先队列可以自动排序,优化了运算过程。不要忘记清空。
4、
链式前向星中:to代表终点,w、c代表权值(可一个或多个权值,看具体题目),next代表以这条边的起点为起点的上一条边的序号。
在使用链式前向星构建图时,模板为有向图,只需要调用一次add函数,该题为无向图,需要将起点与终点互换,调用两次add函数。
5、
vis数组判断该顶点是否被作为权值最小的点而被当做起点用来更新dis和cost。