道路和航线 (拓扑排序+dijkstra / SPFA优化)

题目传送门


题目描述
Farmer John正在一个新的销售区域对他的牛奶销售方案进行调查。他想把牛奶送到T个城镇 (1 <= T <= 25,000),编号为1T。这些城镇之间通过R条道路 (1 <= R <= 50,000,编号为1到R) 和P条航线 (1 <= P <= 50,000,编号为1到P) 连接。每条道路i或者航线i连接城镇A_i (1 <= A_i <= T)到B_i (1 <= B_i <= T),花费为C_i。对于道路,0 <= C_i <= 10,000;然而航线的花费很神奇,花费C_i可能是负数(-10,000 <= C_i <= 10,000)。道路是双向的,可以从A_i到B_i,也可以从B_i到A_i,花费都是C_i。然而航线与之不同,只可以从A_i到B_i。事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台 了一些政策保证:如果有一条航线可以从A_i到B_i,那么保证不可能通过一些道路和航线从B_i回到A_i。由于FJ的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。他想找到从发送中心城镇S(1 <= S <= T) 把奶牛送到每个城镇的最便宜的方案,或者知道这是不可能的。
输入
第1行:四个空格隔开的整数: T, R, P, and S * 第2到R+1行:三个空格隔开的整数(表示一条道路):A_i, B_i 和 C_i * 第R+2到R+P+1行:三个空格隔开的整数(表示一条航线):A_i, B_i 和 C_i

输出
第1到T行:从S到达城镇i的最小花费,如果不存在输出”NO PATH”。

样例输入
6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10
样例输出
NO PATH
NO PATH
5
0
-95
-100


建图

分析

求起点到各个点的最短距离,尝试了用SPFA,但是这题数据卡SPFA,不过可以用SLF优化来过,代码贴在后面。
正解:
注意到无向边边权是非负的,这提示我们可以在无向边上跑最短路。并且我们可以知道,如果将无向边连接的点看作一个整体,最后图中只剩下有向边的话,这个图就是一个有向无环图。
所以我们就可以分开考虑,首先求出若干个由无向边组成的连通块,对于块内的点,通过堆优化的dijkstra算法更新最短路;然后在块与块之间,类似于拓扑排序,一层一层地进行更新,最后就可以求出源点s到所有点的最短路了。总的来说就是拓扑排序+dijkstra的方法。

如何划分连通块
可以用在输入了道路之后深度搜索每一个点,因为此时还未读入航线,所以连通块是分开的。
划分完连通块后
可以将此图划分为三个连通块(block[1] block[2] block [3]),建立一个预处理队列,将s存在的连通块以及入度为0的连通块放入预处理队列中,逐个取出进行处理,先在连通块内部进行dijkstra,当涉及到跨越连通块的边(也就是航线)时,若处理完此边并删去后另一个连通块的入度为0,则将此连通块放入预处理队列(拓扑排序操作)。

拓扑排序+dijkstra代码

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;
const int N=50005,M=100005;
int n,r,p,s;
struct edge{//edge存边 
	int v,next,w;
}e[M<<1];
struct node{
	int cost,u;
	bool operator < (const node &A)const{//用于优先队列排序,cost小的优先 
		return cost>A.cost;
	}
};
int head[N],tot;
void adde(int u,int v,int w){//加边 
	e[tot].v=v,e[tot].w=w,e[tot].next=head[u],head[u]=tot++;
}
bool vis[N];
int cnt;
int bl[N],in[N];//bl存每个点所在的连通块,in存每个连通块的入度 
int cost[N];//到每个点的最小花费 
vector<int>block[N];//存每个连通块的元素 

void dfs(int u)//dfs划分连通块 
{
	vis[u]=1;
	bl[u]=cnt;
	block[cnt].push_back(u);
	for(int i=head[u];i!=-1;i=e[i].next)
	{
		int v=e[i].v;
		if(vis[v])continue;
		dfs(v);
	}
}

void solve()//拓扑排序+Dijkstra 
{
	memset(vis,0,sizeof(vis));
	memset(cost,0x7f,sizeof(cost));
	cost[s]=0;
	queue<int>Q;
	Q.push(bl[s]);
	for(int i=1;i<=cnt;i++)if(!in[i])Q.push(i);
	priority_queue<node>q;
	while(!Q.empty())//拓扑排序循环 
	{
		int blo=Q.front();Q.pop();
		int blosize=block[blo].size();//将连通块内所有点放入q队列用于Dijkstra 
		for(int i=0;i<blosize;i++)q.push(node{cost[block[blo][i]],block[blo][i]});
		while(!q.empty())//Dijkstra
		{
			node now=q.top();q.pop();
			int u=now.u;
			if(vis[u])continue;
			vis[u]=1;
			for(int i=head[u];i!=-1;i=e[i].next)
			{
				int v=e[i].v;
				if(cost[v]>cost[u]+e[i].w)
				{
					cost[v]=cost[u]+e[i].w;
					if(bl[v]==bl[u])q.push(node{cost[v],v});
				}
				if(bl[v]!=bl[u]&&(--in[bl[v]])==0)Q.push(bl[v]);//拓扑排序 
			}
		}
	}
}

int main()
{
	scanf("%d %d %d %d",&n,&r,&p,&s);
	memset(head,-1,sizeof(head));
	for(int i=1;i<=r;i++)//输入道路 
	{
		int a,b,c;
		scanf("%d %d %d",&a,&b,&c);
		adde(a,b,c);
		adde(b,a,c);
	}
	for(int i=1;i<=n;i++)//划分连通块 
	{
		if(!vis[i])
		{
			cnt++;
			dfs(i);
		}
	}
	for(int i=1;i<=p;i++)//输入航线 
	{
		int a,b,c;
		scanf("%d %d %d",&a,&b,&c);
		adde(a,b,c);
		in[bl[b]]++;
	}
	solve();//拓扑排序+Dijkstra 
	for(int i=1;i<=n;i++)
	{
		if(cost[i]>INF)printf("NO PATH\n");
		else printf("%d\n",cost[i]);
	}
	return 0;
}

SPFA SLF优化代码

#include<bits/stdc++.h>
using namespace std;
#define N 25010
#define M 150010
#define Inf 0x3f3f3f3f
int e[M],h[N],idx,w[M],ne[M];
int n,r,p,s;
int vis[N];
int cost[N];
deque<int>q;

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

void spfa()
{
	for(int i=1;i<=n;i++)cost[i]=Inf;
	memset(vis,0,sizeof(vis));
	vis[s]=1;
	q.push_back(s);
	cost[s]=0;
	while(!q.empty())
	{
		int now;
		now=q.front();
		q.pop_front();
		vis[now]=0;
		for(int i=h[now];i>0;i=ne[i])
		{
			int u=e[i];
			if(cost[u]>cost[now]+w[i])
			{
				cost[u]=cost[now]+w[i];
				if(!vis[u])
				{
					vis[u]=1;
					if(!q.empty()&&cost[q.front()]>cost[u])q.push_front(u);
					else q.push_back(u);
				}
			}
		}
	}
}

int main()
{
	int a,b,c;
	scanf("%d %d %d %d",&n,&r,&p,&s);
	for(int i=1;i<=r;i++)
	{
		scanf("%d %d %d",&a,&b,&c);
		addd(a,b,c);
		addd(b,a,c);
	}
	for(int i=1;i<=p;i++)
	{
		scanf("%d %d %d",&a,&b,&c);
		addd(a,b,c);
	}
	spfa();
	for(int i=1;i<=n;i++)
	{
		if(cost[i]==Inf)
		{
			printf("NO PATH\n");
			continue;
		}
		printf("%d\n",cost[i]);
	}
	return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值