最短路问题

\(Floyd\)

多源最短路算法,用邻接矩阵存储,可以处理负边权,不能处理负环,本质是个\(DP\)

需要三层循环,时间复杂度为\(O(n^{3})\),在稠密图中有优势。

记得\(dis\)数组要初始化成正无穷(不相连的点之间),并且自己到自己的距离为\(0\),即\(dis[i][i]=0\)

\(code\)

for(int k=1;k<=n;++k)
{
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=n;++j)
		{
			if(i==j||i==k||j==k) continue;
			if(dis[i][j]>dis[i][k]+dis[k][j])
				dis[i][j]=dis[i][k]+dis[k][j];
		}
	}
}

\(Dijkstra\)

原本时间复杂度为\(O(n^{2})\),用堆优化后,复杂度为\(O((n+m)log\ n)\),在稀疏图中有优势。

\(code\)

struct node
{
	int val;
	int num;
	friend bool operator <(const node &x,const node &y)
	{
		return x.val>y.val;
	}
};	
priority_queue<node> q;
int dis[maxn];
bool vis[maxn];
void dijkstra()
{
	for(int i=1;i<=n;++i) dis[i]=inf;
	dis[s]=0;
	q.push((node){0,s});
	while(!q.empty())
	{
		node tmp=q.top();
		q.pop();
		int x=tmp.num;
		if(vis[x]) continue;
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt)
		{
			int y=e[i].to,v=e[i].v;
			if(dis[y]>dis[x]+v)
			{
				dis[y]=dis[x]+v;
				q.push((node){dis[y],y});
			}
		}
	}
}

\(SPFA\)

\(Bellmam-Ford\)算法加上队列优化,可以处理负边权,能判断负环。判负环跑最短路,判正环跑最长路。

时间复杂度最坏情况会退化为\(O(nm)\),会被卡,所以它死了

如果图是随机生成的,时间复杂度为\(O(km)\)\(k\)可以认为是个常数)。

\(SPFA\)的过程是\(bfs\),是在不断扩展节点。

\(SPFA\)求最长路问题,考虑到\(SPFA\)处理负边权的特性,可以把边权取相反数,然后跑\(SPFA\),最后再取相反数输出就是最长路了。

正边权(数据相对较大)时,不要用\(SPFA\)

\(code\)

int dis[maxn];
bool vis[maxn];
void spfa()
{
	queue<int> q;
	for(int i=1;i<=n;++i) dis[i]=inf;
	q.push(s);
	dis[s]=0;
	vis[s]=true;
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		vis[x]=false;
		for(int i=head[x];i;i=e[i].nxt)
		{
			int y=e[i].to,v=e[i].v;
			if(dis[y]>dis[x]+v)
			{
				dis[y]=dis[x]+v;
				if(!vis[y])
				{
					vis[y]=true;
					q.push(y);
				}
			}
		}
	}
}

其他

当图中存在两个状态时,如请柬有前往费用和返回费用,最优贸易有买入价和卖出价,这时不妨考虑再建一个反图,正向图反向图各跑一遍最短路。

有时不易发现用最短路,要将题意理清楚,推推式子,如大圣诞树(暑假欢乐赛\(T2\)),当时已经把式子推出来了,但傻傻的打了\(40\)分的暴力。

同余最短路,并不是用同余来跑最短路,而是通过同余来构造某些状态,从而达到优化时间空间复杂度的目的。如跳楼机

分层图最短路,有一些动态规划的思想,建出多层的原图来跑最短路,如飞行路线冻结

\(code\)

for(int i=1;i<=m;++i)
{
	int a,b,v;
	read(a),read(b),read(v);
	add(a,b,v),add(b,a,v);
	for(int j=1;j<=k;++j)
	{
		add(a+(j-1)*n,b+j*n,val);
		add(b+(j-1)*n,a+j*n,val);
		add(a+j*n,b+j*n,v);
		add(b+j*n,a+j*n,v);
	}
}

其中\(val\)为越层的花费

\(val\)\(0\),则需加上

\(code\)

for(int i=1;i<=k;++i) add(t+(i-1)*n,t+i*n,0);

来防止到终点的路径边数小于层数

\(val\)不为\(0\),则需加上

\(code\)

for(int i=0;i<=k;++i) ans=min(ans,dis[n+i*n]);

来保证答案为最优解

最短路计数问题,问从\(s\)\(t\)有几条最短路

单源最短路,在松弛时,若发现相等则路径条数更新,应用加法原理

\(code\)

void dijkstra()
{
	for(int i=1;i<=n;++i) dis[i]=inf;
	dis[1]=0;
	tot[1]=1;
	q.push((node){0,1});
	while(!q.empty())
	{
		node tmp=q.top();
		q.pop();
		int x=tmp.num;
		if(vis[x]) continue;
		vis[x]=true;
		for(int i=head[x];i;i=e[i].nxt)
		{
			int y=e[i].to,v=e[i].v;
			if(dis[y]>dis[x]+v)
			{
				dis[y]=dis[x]+v;
				tot[y]=tot[x];
				q.push((node){dis[y],y}); 
			}
			else if(dis[y]==dis[x]+v)
				tot[y]=tot[x]+tot[y];//更新路径条数
		}
	}
}

多源最短路,同理,在松弛时,若发现相等则路径条数更新,应用乘法原理

\(code\)

for(int i=1;i<=m;++i)
{
	int a,b,v;
	scanf("%d%d%d",&a,&b,&v);
	dis[a][b]=dis[b][a]=min(dis[a][b],v);
	tot[a][b]=tot[b][a]=1;//初始化
}
for(int k=1;k<=n;++k)
{
	for(int i=1;i<=n;++i)
	{
		if(k==i) continue;
		for(int j=1;j<=n;++j)
		{
			if(k==j||i==j) continue;
			if(dis[i][j]>dis[i][k]+dis[k][j])
			{
				dis[i][j]=dis[i][k]+dis[k][j];
				tot[i][j]=tot[i][k]*tot[k][j];
			}
			else if(dis[i][j]==dis[i][k]+dis[k][j])//表明k在i到j的最短路上
				tot[i][j]+=tot[i][k]*tot[k][j];//更新路径条数
		}	
	}
}
posted @ 2020-01-22 20:01  lhm_liu  阅读(238)  评论(0编辑  收藏  举报