洛谷P3371&&P4849单源最短路【模板】

最短路是图论部分经常坑熊孩子的部分,一般会和dp一起考。

求指定起点到图上所有点的最短路径

 

以洛谷 P3371 为例 地址 https://www.luogu.org/problemnew/show/P3371

最常用的方法 有

Floyd 复杂度O(n^3);

floyd的过程相当于对每个点都进行了三角形松弛操作,就是假如a->b+b->c   >     a->c,那么显然从a->b->c比从a->c优,然后就没了

因为该算法求出了所有点间的最短路

目测不能通过

//By AcerMo
#include
#include
#include
#include
#include
#include
using namespace std;
int a[5000][5000],n,m,st;//用邻接矩阵来存图即可,因为数组开大了会RE
inline int read()
{
    int x=0;char ch=getchar();
    while (ch>'9'||ch<'0') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
inline void floyd()
{
    for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
    {
        if(i==k||a[i][k]==2147483647) continue;//优化 
        for(int j=1;j<=n;j++)
        {
            if(i==j||j==k||a[k][j]==2147483647) continue;
            a[i][j]=min(a[i][j],a[i][k]+a[k][j]);//从i到j的当前最短路径与从i到k再到j的路径取min
        }
    }
    return ;
}
signed main()
{
    n=read(),m=read(),st=read();
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    a[i][j]=2147483647;//初始化路径长为正无穷
    for(int i=1,u,v,w;i<=m;i++)
    {
         u=read(),v=read(),w=read();
         a[u][v]=min(a[u][v],w);
    }
    a[st][st]=0;floyd(); 
    for(int i=1;i<=n;i++) printf("%d ",a[st][i]); 
    return 0;
}

之后是

Dijkstra 复杂度O(n^2)

目测数据太大也过不了

思想:通过一个已经确定最短路的点来更新与其相连的点的最短路

蓝白点思想,用两个颜色表示有没有确定最短路

代码

//By AcerMo
#include
#include
#include
#include
#include
#include
using namespace std;
const int inf=2147483647;
const int M=200500;
int m,n,st; 
int dis[M];// 从起点到i点的最短路径 QAQ 
bool jud[M];//判断颜色
            //0->白色,已确定最短路径,1->蓝色,未被开发 
int to[M],nxt[M],head[M],w[M],cnt;
inline int read()
{
    int x=0;char ch=getchar();
    while (ch>'9'||ch<'0') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
inline void add(int x,int y,int z)
{
	to[++cnt]=y;nxt[cnt]=head[x];w[cnt]=z;head[x]=cnt;
	return ;
}
inline void dijkstra()
{
    fill(jud,jud+n+1,1);//1说明没走过,蓝点=-= 
    fill(dis,dis+n+1,inf);//最短路径置成无穷 
    dis[st]=0;//起点到起点的路径长为0 
    while (1)//循环找起点通过每个点到其他点的最短路径 
    {
        int v=-1;
        for (int i=1;i<=n;i++)
            if ((v==-1||dis[v]>dis[i])&&jud[i]) v=i;//距离起点目前最近的点 
        if (v==-1) break; //所有点都已找到最短路径 
        jud[v]=0;//该点标记为 白点
        for (int i=head[v];i;i=nxt[i])
		dis[to[i]]=min(dis[to[i]],dis[v]+w[i]); 
    }
    return ;
}
signed main()
{
    n=read();m=read();st=read();int x,y,z;
    for (int i=1;i<=m;i++)
    x=read(),y=read(),z=read(),add(x,y,z);
    dijkstra();
    for (int i=1;i<=n;i++) printf("%d ",dis[i]);
    return 0;
}

Dijkstra可以使用优先队列优化,复杂度隐约是O(nlogn);可以通过

优先队列的用途是省去O(n)查找当前最短路,每次都将最短路放在队首

保证用图中距离起点最近的点去更新其他点

代码

//By AcerMo
#include
#include
#include
#include
#include
#include
using namespace std;
const int M=1000500;
struct heap
{
	int to,cost;
	bool friend operator < (heap a,heap b)
	{
		return a.cost>b.cost;
	}
}add,now;
priority_queueq;
int n,m,st;
int dis[M],vis[M];
int to[M],nxt[M],head[M],w[M],cnt;
inline int read()
{
	int x=0;char ch=getchar();
	while (ch>'9'||ch<'0') ch=getchar();
	while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return x;
}
inline void adda(int x,int y,int z)
{
	to[++cnt]=y;nxt[cnt]=head[x];w[cnt]=z;head[x]=cnt;
	return ;
}
inline void dijkstra()
{
	fill(dis,dis+n+1,2147483647);dis[st]=0;
	add.to=st;add.cost=0;q.push(add);
	while (q.size())
	{
		now=q.top();q.pop();
		if (vis[now.to]||dis[now.to]!=now.cost) continue;vis[now.to]=1;
		for (int i=head[now.to];i;i=nxt[i])
		{
			if (dis[to[i]]>dis[now.to]+w[i])
			{
				dis[to[i]]=dis[now.to]+w[i];
				add.to=to[i];add.cost=dis[to[i]];q.push(add);
			}	
		} 
	}
	return ;
}
signed main()
{
	n=read();m=read();st=read();int x,y,z;
	for (int i=1;i<=m;i++)
		x=read(),y=read(),z=read(),adda(x,y,z);
	dijkstra();
	for (int i=1;i<=n;i++) printf("%d ",dis[i]);
	return 0;
}

Bellman-Ford算法 O(NE)还是会TLE

类似Floyd,用了一个松弛操作,即通过一个点来判断另外的点之间的边是否松弛,但是该算法枚举的是所有边;

//By AcerMo
#include
#include
#include
#include
#include
#include
using namespace std; 
const int M=500500;
int n,m,st;
int dis[M],u[M],v[M],w[M];
inline int read()
{
    int x=0;char ch=getchar();
    while (ch>'9'||ch<'0') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
inline int Bellman_Ford()
{
	dis[st]=0;bool check=0,flag=0;
	for(int k=1;k<=n-1;k++)  //Bellman-Ford的算法 
    {  
        for(int i=1;i<=m;i++)  
            if((long long)dis[u[i]]+w[i]

O(NE)很快?NO!还存在一个叫做SPFA的算法,是Bellman-Ford算法的队列实现,可以将复杂度降到O(KE),但是如果建成的图为规则方阵,则会比较慢,现在都8102年了,还是去用heap+dij吧

SPFA O(KE)

每次都从队列里取出一个元素,修改与它临近的所有点的最小值,修改成功,则入队

//By AcerMo
#include
#include
#include
#include
#include
#include
using namespace std; 
const int M=500500;
struct edge{int to,cost;}t;
int dis[M],vis[M];
int n,m,st;
vectorv[M];//邻接表存储 
inline int read()
{
    int x=0;char ch=getchar();
    while (ch>'9'||ch<'0') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
inline void SFPA()
{
    fill(dis,dis+n+1,2147483647);//fill函数类似memset,填充指定区间 
	dis[st]=0;queueq;q.push(st);
    while(q.size())//队列不为空即点还没有修改完 
    {
        int u=q.front();q.pop();vis[u]=0;//掰回没到过 
        for(int i=0;idis[u]+pay)//松弛 
            {
                dis[go]=dis[u]+pay;
                if(!vis[go])//加入队列 
                    vis[go]=1,q.push(go);
            }
        }
    }
    return ;
}
int main()
{
    n=read();m=read();st=read();int x;
    for(int i=1;i<=m;i++)
    {
        x=read();t.to=read();t.cost=read();
        v[x].push_back(t);
    }
    SFPA();
    for (int i=1;i<=n;i++) printf("%d ",dis[i]);
    return 0;
}

依据数据大小使用;

PS  Floyd,Dijkstra算法无法判断存在负权问题

 

 

 

 

 

 

你可能感兴趣的:(图论-最短路)