首页 > 其他分享 >二修最短路

二修最短路

时间:2022-10-12 21:14:54浏览次数:62  
标签:head vis 二修 短路 int push 复杂度 dis

观看前提示:本人的码风可能会引起您的不适。
写作时间:2022-9-10~2022-10-12.

1.Floyd全源最短路

1-1 朴素

弗洛伊德,枚举每个点然后进行松弛,这样可以在 \(\Theta(n^{3})\) 时间里求出每个点的最短路。

写起来很像dp。

for(int i=1;i<=n;i++)
{
    for(int j=1;j<=n;j++)
    {
        for(int k=1;k<=n;k++)
        {
            ans[j][k]=min(ans[j][k],ans[j][i]+ans[i][k]);
        }
    }
}

这是最朴素的Floyd算法,时间复杂度为 \(\Theta(n^3)\)。

Floyd算法优化也有,但是实际上对时间复杂度的优化并没有多少。

如果要考Floyd,数据范围不会很大的。

切记切记。

1-2 传递闭包

这东西只需要改改条件罢。

for(int i=1;i<=n;i++)
{
    for(int j=1;j<=n;j++)
    {
        for(int k=1;k<=n;k++)
        {
            if(ans[j][i] && ans[i][k])
            {
                ans[j][k]=1;
            }
        }
    }
}

2.dijkstra单源最短路算法

2-1 朴素

void dijkstra(int u)
{
	queue<int>q;
	fill(dis,dis+n,2147483647);
	dis[u]=0;
	vis[u]=1;
	q.push(u);
	while(q.empty()==false)
	{
		int cur=q.front();
		q.pop();
		vis[cur]=0;
		for(int i=head[cur];i;i=e[i].nxt)
		{
			int v=e[i].v;
			int w=e[i].w;
			if(dis[v]>dis[cur]+w)
			{
				dis[v]=dis[cur]+w;
				pre[cur]=v;
				if(vis[v]==0)
				{
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
}

我们使用了一个队列来让当前节点入队。

时间复杂度为 \(\Theta(n^{2})\)

2-2 堆优化/优先队列优化

比较广泛的是堆优化。

    fill(dis,dis+BI,iINF);
    dis[s]=0;
    q.push(make_pair(0,s));
    while(!q.empty())
    {
        int tmp=q.top().second;
        q.pop();
        if(vis[tmp])
        {
            continue;
        }
        vis[tmp]=1;
        for(int i=head[tmp];i;i=e[i].nxt)
        {
            int v=e[i].v;
            int w=e[i].w;
            if(dis[v]>dis[tmp]+w)
            {
                dis[v]=dis[tmp]+w;
                q.push(make_pair(-dis[v],v));
            }
        }
    }

这一段代码中,q是一个优先队列,它的定义是:

priority_queue<pair<int,int>>q;

省去了查找最大值,时间复杂度为 \(\Theta((n+m)\log n)\)。

一般使用优先队列优化的 Dijkstra,因为并没有难写多少,而且时间复杂度更优,绝大多数时候堆优化的Dijkstra就够了。

缺点是边权不能为负数。

3.SPFA及其优化

3-1

我们都知道,朴素的SPFA及其容易被卡:

fill(dis,dis+n,2147482347);
int spfa()
{
    queue<int>q;
    q.push(1);
    dis[1]=0;
    vis[1]=true;
    while(q.size())
    {
        int u=q.front();
        q.pop();
        vis[t]=false;
        for(int i=head[u];i;i=e[i].nxt)
        {
            int j=e[i].v;
            if(dis[j]>dis[t]+e[i].w)
            {
                dis[j]=dis[t]+e[i].w;
                if(!vis[j])
                {
                    vis[j]=true;
                    q.push(j);
                }
            }
        }
    }
    if(dis[n]==2147482347)
    {
        return -1;
    }
    return dis[n];
}

如果没有被卡,时间复杂度是 \(\Theta(k\times m)\),一般情况下,\(k\) 大约在 \(1-20\) 之间。

但是非常容易被卡,时间复杂度会退化到 \(\Theta(n\times m)\),这个时候无法通过大部分数据。

所以SPFA这个东西,实际上是不靠谱的。

但是它能够用来判断负环,对于差分约束系统是很有用的。

3-2 优化

3-2-1 SLF

直接把

queue<int>q;

改成

deque<int>q。
int spfa()
{
    deque<int>q;
    q.push_back(1);
    dis[1]=0;
    vis[1]=true;
    while(q.size())
    {
        int u=q.front();
        q.pop_front();
        vis[t]=false;
        for(int i=head[u];i;i=e[i].nxt)
        {
            int j=e[i].v;
            if(dis[j]>dis[t]+e[i].w)
            {
                dis[j]=dis[t]+e[i].w;
                if(!vis[j])
                {
                    vis[j]=true;
                    q.push_back(j);
                }
            }
        }
    }
    if(dis[n]==2147482347)
    {
        return -1;
    }
    return dis[n];

实际上并没有优化多少,还是很容易被卡到 \(\Theta(n\times m)\).

3-2-2 LLL

不是很会写,这实际上是LLL+SLF,朴素LLL优化把队列换回queue就好了。

void spfa()
{
	int u,v,num=0;
	long long x=0;
	list<int>q;
	for(int i=1; i<=n; i++)
	{
		dis[i]=MAX;
		vis[i]=false;
	}
	dis[s]=0;
	vis[s]=true;
	q.push_back(s);
	num++;
	while(!q.empty())
	{
		u=q.front();
		q.pop_front();
		num--;
		x-=dis[u];
		while(num&&dis[u]>x/num)
		{
			q.push_back(u);
			u=q.front();
			q.pop_front();
		}
		vis[u]=false;
		for(int i=head[u]; i; i=e[i].next)
		{
			v=e[i].to;
			if(relax(u,v,e[i].w)&&!vis[v])
			{
				vis[v]=true;
				if(!q.empty()&&dis[v]<dis[q.front()])
                {
                    q.push_front(v);
                }
				else
                {
                    q.push_back(v);
                }
				num++;
				x+=dis[v];
			}
		}
	}
   	for(int i=1; i<=n; i++)
   	{
   	    printf("%d ",dis[i]);
   	}
	printf("\n");
}

还是很容易被卡,但是起码被卡之后比朴素快一点。

3-2-3

NTR算法,来自知乎Raffica的写法

对,不过不如尝试以下奇怪的方法来避免被卡:(原创!)维护一个叫临时最短路树的东西,刚开始只有一个源节点这SPFA的过程中,每次松弛(u, v)边时将v的父亲设为u;v是有可能有后代的,所以将其所有后代的对应inqueue标记清除;在SPFA过程中如果发现队头节点的inqueue为空那么跳过。理由是如果我们能松弛(u, v)边,那么从(u, v)走势必比以前走过的那种走法好。将这个步骤称为NTR。
以上为用lct实现的这东西的代码的一部分(当时在测试lct模板 事实上正常来讲用lct反而更慢(说明 最终不用lct)
当有负权环时这个算法也会出现环,所以可以用来快速找环。
这个算法会被奇怪的图卡掉。但是网格图卡不掉。

作者:Raffica
转载注明出处。

int SPFA()
{
	int i, j, x, cnt = 0;
	queue<int> Q;
	memset(d, 0x3f, sizeof(d));
	d[S] = 0;
	Q.push(S);
	setv(S, 1);
	while(!Q.empty())
	{
		x = Q.front();
		Q.pop();
		cnt ++;
		if(!query(x)) continue;
		for(i = 0;i < G[x].size();i	++)
		{
			Edge &e = edge[G[x][i]];
			if(d[x] + e.w < d[e.v])
			{
				d[e.v] = d[x] + e.w;
				Q.push(e.v);
				setc(e.v, 0);
				setv(e.v, 1);
				if(lca(e.v, x) == e.v)
				{
					return 0;
				}
				int f = getfa(e.v);
				linkcut(f, e.v, x);
			}
		}
	}
	return 1;
}

时间复杂度貌似是 \(\Theta(n\log n\log (m/n)+m)\),即 \(\Theta(n\log n\log (m/n))\) 试了一下是能过hack数据的。

3-2-4 SLFS

就是SLF加上了一个swap算法,实际实现可以看CSDN扩展的灰的代码

void work()
{
    int l,r;
    t=1;l=r=0;
    memset(dis,iINF,sizeof(dis));
    dis[s]=0;
    q[r++]=s;
    while(l!=r)
    {
        int u=q[l++];
        if(l>n)
        {
            l=0;
        }
        if(cnt[u]>t)
        {
            q[r++]=u;
            if(r>n) r=0;
            t+=20;
            continue;
        }
        vis[u]=0;
        for(int i=head[u];i;i=e[i].nxt)
        {
            int v=e[i].v;
            int w=e[i].w;
            int d=dis[u]+w;
            if(d<dis[v])
            {
                dis[v]=d;
                if(!vis[v])
                {
                    q[r++]=v;
                    ++cnt[u];
                    if(r>n)
                    {
                        r=0;
                    }
                    vis[v]=1;
                }
                int rr;
                if(r-1<0)
                {
                    rr=n;
                }
                else
                {
                    rr=r-1;
                }
                if(dis[q[l]]>dis[q[rr]])
                {
                    swap(q[l],q[rr]);
                }
            }
        }
    }
    for(int i=1;i<=n;++i)
    {
        put(dis[i]);
        putchar(' ');
    }
}

每当队列改变时,如果队首距离大于队尾,则交换首尾。
这个 SLF 看起来很弱,但却通过了所有 Hack 数据。而且,非常难卡。
能够过掉hack数据,但是实际上还是能卡的。

3-2-5 堆优化

一般的算法都是让队列接近优先队列,现在是直接定义一个优先队列。

void SPFA()
{
    priority_queue<int, vector<int>, cmp> q;
    q.push(s);
    memset(dis, 0x3f, sizeof(dis));
    dis[s] = 0;
    vis[s] = true;
    while (!q.empty())
    {
        int u = q.top();
        q.pop();
        vis[u] = false;
        for (int i = 0; i < v[u].size(); i++)
        {
            if (dis[v[u][i]] <= dis[u] + w[u][i]) continue;
            dis[v[u][i]] = dis[u] + w[u][i];
            if (vis[v[u][i]]) continue;
            q.push(v[u][i]);
            vis[v[u][i]] = true;
        }
    }
}

这里的cmp函数按照dis来排序。

复杂度正确,能通过hack数据。

3-2-6 乱序

加一个

for(int i = 1 ; i <= n ; i++)
{
	random_shuffle(e[i].begin() ,e[i].end()); 
}

3-2-7 mcfx 优化

在第 \([ l,r]\) 次访问一个结点时,将其放入队首,否则放入队尾。通常取 \(l=2,r=\sqrt{v}\) 。
写不来,溜了。

3-2-8 SLF容错优化

每次将入队结点距离和队首比较,如果比队首大超过一定值则插入至队尾。
我们定义容错值 \(val\),当满足 \(dis[now]>dis[q.front()]+val\) 时从队尾插入,否则从队首插入。
设w为边权之和,\(val\) 一般为 \(\sqrt{w}\)
容错SLF可以让你的程序不陷入局部最优解,与模拟退火类似

3-2-9 随机化优化

除了乱序以外:
队列随机:每个节点入队时,以0.5的概率从队首入队,0.5的概率从队尾入队。
队列随机优化版:每 \(x\) 次入队后,将队列元素随机打乱。

3-3 Hack it!

显然,普通 SPFA 是非常好卡的,只需要一个随机网格图即可。

当你用到优化的时候,也就不用卡了。

3-4 用途

  1. 判负环;
  2. 如果边权有时候为负的单源最短路只能首选SPFA。
  3. 求最小费用最大流
  4. 次短路...?
  5. 没了,用Dijkstra。

4.johnson全源最短路

和 Floyd 一样,是一种能求出无负环图上任意两点间最短路径的算法。

可以说是一种集大成者。

我们新建一个虚拟节点(在这里我们就设它的编号为\(0\))。从这个点向其他所有点连一条边权为\(0\)的边。

接下来用SPFA算法求出从\(0\)号点到其他所有点的最短路,记为 \(dis\)

假如存在一条从点 \(u\) 到点 \(v\),边权为 \(w\) 的边,则我们将该边的边权重新设置为 \(w += dis[u] - dis[v]\);

接下来以每个点为起点,跑 \(n\) 轮 Dijkstra 算法即可求出任意两点间的最短路了。

最后加上 \(dis[v] - dis[u]\);

容易看出,该算法的时间复杂度是 \(\Theta(nm\log m)\)。

//火车头省略。
struct node
{
    int dis;
    int id;
    bool operator<(const node& a) const
	{
		return dis > a.dis;
	}
	node(int d, int x)
	{
		dis = d, id = x;
	}
}
int head[5005];
int vis[5005];
int t[5005];
int cnt, n, m;
int h[5005];
int dis[5005];
void add(int u, int v, int w)
{
	e[++cnt].v = v;
	e[cnt].w = w;
	e[cnt].next = head[u];
	head[u] = cnt;
}
bool spfa(int s)
{
	queue<int> q;
	fill(h,h+KI,iINF)
	h[s] = 0, vis[s] = 1;
	q.push(s);
	while (!q.empty())
	{
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for (int i = head[u]; i; i = e[i].next)
		{
			int v = e[i].v;
			if (h[v] > h[u] + e[i].w)
			{
				h[v] = h[u] + e[i].w;
				if (!vis[v])
				{
					vis[v] = 1;
					q.push(v);
					t[v]++;
					if (t[v] == n + 1) return false;
				}
			}
		}
	}
	return true;
}
void dijkstra(int s)
{
	priority_queue<node> q;
	fill(dis,dis+KI,iINF);
	memset(vis, 0, sizeof(vis));
	dis[s] = 0;
	q.push(node(0, s));
	while (!q.empty())
	{
		int u = q.top().id;
		q.pop();
		if (vis[u]) continue;
		vis[u] = 1;
		for (int i = head[u]; i; i = e[i].next)
		{
			int v = e[i].v;
			if (dis[v] > dis[u] + e[i].w)
			{
				dis[v] = dis[u] + e[i].w;
				if (!vis[v])
                {
                    q.push(node(dis[v], v));
                }
			}
		}
	}
	return;
}
int main()
{
	for (int i = 1; i <= m; i++)
	{
		int u=read();
        int v=read();
        int w=read();
		add(u, v, w);
	}
	for (int i = 1; i <= n; i++)
    {
        add(0, i, 0);
    }
	if (!spfa(0))
	{
		cout << -1 << endl;
		return 0;
	}
	for (int u = 1; u <= n; u++)
    {
		for (int i = head[u]; i; i = e[i].next)
        {
            e[i].w += h[u] - h[e[i].v];
        }
    }
	for (int i = 1; i <= n; i++)
	{
		dijkstra(i);
		long long ans = 0;
		for (int j = 1; j <= n; j++)
		{
			if (dis[j] == INF)
				ans += j * INF;
			else
				ans += j * (dis[j] + h[j] - h[i]);
		}
		cout << ans << '\n';
	}
	return 0;
}

标签:head,vis,二修,短路,int,push,复杂度,dis
From: https://www.cnblogs.com/SoN3ri/p/FDBSJ.html

相关文章

  • TZOJ 7685: 最短路径 (dijstra/输出路径pre)
    描述  给定n个顶点的带权有向图,若从顶点x到顶点y之间存在一条路径,那么这条路径的长度定义为路径上各条边的权值之和。现在请你找出从顶点1到顶点n的一条最短路径。......
  • 图论-最短路算法
    一、floyd1.介绍 floyd算法只有五行代码,代码简单,三个for循环就可以解决问题,所以它的时间复杂度为O(n^3),可以求多源最短路问题。 2.思想: Floyd算法的基本思想如......
  • 平面图最小割转换为对偶图最短路问题
    1、P4001[ICPC-Beijing2006]狼抓兔子 P4001[ICPC-Beijing2006]狼抓兔子-洛谷|计算机科学教育新生态(luogu.com.cn)直接上图,  注意dijkstra!!!  判重......
  • 最短路径问题---Dijkstra算法详解
    0.最短路径问题介绍问题解释:从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径1.Dijkstra算法介绍算法特点:迪科斯彻算法使用......
  • 图论最短路径问题(一)
     图的基本概念总概念:图论中的图是由若干给定的点及连接两点的线构成的图形,表示事物之间的特定关系点:表示事物线:表示相应两个事物之间具有某种的特定关系数学语言描述:G(V(G)......
  • TZOJ 2674: 一个人的旅行 最短路/Floyd
    描述虽然草儿是个路痴(就是在tzc待了一年多,居然还会在校园里迷路的人,汗~),但是草儿仍然很喜欢旅行,因为在旅途中会遇见很多人(白马王子,^0^),很多事,还能丰富自己的阅历,还可以看......
  • dijsktra求最短路径
    讲算法原理的有很多,直接贴代码dijkstra算法是直接对邻接矩阵进行操作求出最短路径的,我项目中的图结构需要转化成邻接矩阵,所以会有下面代码图结构是一个map,first表示节点的in......
  • 最短路径算法
    研究生考试中图论中求解最短路径的算法主要有两种,Dijkstra算法及Floyd算法,其中Dijkstra算法用于求解单源最短路径问题,而Floyd算法则用于解决多源最短路径问题。本文对这两......
  • 分层图之最短路
    P4568[JLOI2011]飞行路线-洛谷|计算机科学教育新生态(luogu.com.cn)可以把K个路径的权值变为0一开始根本没思路,看题解发现可以发现用K次就可以化为K+1层,每层与每......
  • 洛谷 P2419 [USACO08JAN]Cow Contest S(最短路:floyed)
    https://www.luogu.com.cn/problem/P2419题目大意:给定n头奶牛(1<=N<=100),按1..N依次编号。m轮:两两之间进行对决,赢了的排在左边,输了的排在右边。我们想知道奶牛们编......