带SLF和LLL优化的SPFA:342.道路与航线

做前分析

道路距离最短+有负权边==spfa。

起点s,到各个点距离计算,spfa一遍即可,最后循环一遍dis数组就行了

但是,老话说:’spfa好像活着,其实已经死了 ‘,几乎所有一眼spfa题目全部需要优化才行。当初学spfa时,老师口口声声说,时间复杂度最大O(nm),大概率O(m);结果全是O(nm),哭哭哭哭。所以以后遇到spfa算法直接写优化的

SPFA的两种优化

SLF优化

对即将入队的元素进行操作,与队头元素的dis值进行比较,若大于或此时队列为空,放入队尾;若小于,放入队头。(原理不太清楚,查半天没有,大概就是先遍历小的部分更容易吧,有兄弟能给我解释解释吗?)

//对队列头尾都操作,要用双端队列deque

queue<int>s;
s.push_back();//在队尾插入
s.push_front();//在队头插入
s.pop()_back();//删除队尾元素
s.pop_front();//删除队头元素
//其余操作与queue相同,如s.empty(),s.size();

//SLF优化部分代码
//入队是在dis[j]被更新时

//t为此时已经确定最短距离的点,用他来更新与之相连的点到起点的距离
for(int i=h[t];i!=-1;i=ne[i])
{
    int j=e[i];//枚举与t相连的点j
    if(dis[j]>dis[t]+w[i])//dis[j]要被更新
    {
        dis[j]=dis[t]+w[i];
	    if(!st[j])//如果此时j不在队列中
	    {
	        st[j]=1;
            //SLF优化
            if(!q.empty()&&dis[j]<dis[q.front()])q.push_front(j);
			else q.push_back(j);
	    }
    }
}

LLL优化

对出队元素进行操作,队列中所有dis数组值 和为sum,个数为num;如果出队元素t的dis[t]*num>sum,则把t从队头换到队尾(就是dis[t]大于现在在队列中dis的平均值)。

deque<int>q;
dis[s]=0;
q.push_back(s);//s是起点
int sum=0,num=1;//dis[s]为0,所有sum初始为0,队列只有s一个元素,num=1'

while(q.size())
{
    //队头元素出队操作
	int t=q.front();
	while(!q.size())
	{
		if(dis[t]*num>sum)//dis[t]大于现在队列的DIS平均值
		{
            把t从队头调换到队尾
			q.pop_front();
			q.push_back(t);
			t=q.front();//一样的操作直到找到小于平均值的元素
		}
	}
    
	q.pop_front(); st[t]=0;
	num--; sum-=dis[t];

 加入两种优化的spfa代码

注意点:本题道路为无向图,而航路为有向,则N=2*R+P=150010; 

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=150010;
typedef pair<int,int> pll;
#define x first
#define y second

int h[N],e[N],w[N],ne[N],idx;
int t,r,p,s;
int dis[N],st[N];

void add(int a,int b,int c)
{
	e[idx]=b; w[idx]=c; ne[idx]=h[a]; h[a]=idx++;
}

void spfa()
{
	memset(dis,0x3f,sizeof dis);
	dis[s]=0; st[s]=1;
	deque<int>q;
	q.push_back(s);
	int sum=0,num=1;
	
	while(q.size())
	{
		int t=q.front();
		while(!q.size())
		{
			if(dis[t]*num>sum)
			{
				q.pop_front();
				q.push_back(t);
				t=q.front();
			}
		}
		q.pop_front(); st[t]=0;
		num--;
		sum-=dis[t];
		
		for(int i=h[t];i!=-1;i=ne[i])
		{
			int j=e[i];
			if(dis[j]>dis[t]+w[i])
			{
				dis[j]=dis[t]+w[i];
				if(!st[j])
				{
					st[j]=1;
					sum+=dis[j]; num++;
					if(!q.empty()&&dis[j]<dis[q.front()])q.push_front(j);
					else q.push_back(j);
				}
			}
		}
	}
}

int main()
{
	memset(h,-1,sizeof h);
	
	cin>>t>>r>>p>>s;
	for(int i=1;i<=r;i++)
	{
		int a,b,c; scanf("%d%d%d",&a,&b,&c);
		add(a,b,c); add(b,a,c);
	}
	for(int i=1;i<=p;i++)
	{
		int a,b,c; scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
	}
	
	spfa();
	
	for(int i=1;i<=t;i++)
	{
		if(dis[i]==0x3f3f3f3f)printf("NO PATH\n");
		else cout<<dis[i]<<endl;
	}
	
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值