【最短路】P3008 [USACO11JAN]Roads and Planes G

本文介绍了一种解决含有负权边的有向图最短路径问题的方法,通过Tarjan算法进行缩点处理,将图转化为DAG,再利用拓扑排序和Dijkstra算法计算最短路径。

题目描述

有两种边:1.无向无负边权 2.有向有负边权

由于负边权的存在,无法直接使用dijkstra

而这道题目会卡spfa,因此我们需要去考虑其他的方法

 

我们可以先将第1种边连上,然后进行tarjan缩点(貌似并查集会更简单)

然后,对于生成的DAG图,可以用拓扑来求每个缩点后的点之间的最短路,每个集团内部的最短路,可以用dijkstra来计算

 

代码

#include<bits/stdc++.h>
using namespace std;
const pair<int,int> PII;
const int maxn=50000+5;
const int inf=0x3f3f3f3f;
int t,r,p,s,tot,times,in[maxn],col[maxn],instack[maxn],head[maxn],fir[maxn],dfn[maxn],low[maxn],vis[maxn],col_num;
struct edge
{
	int to,nxt,v;
}e[maxn<<1],m[maxn<<1];
void add(int x,int y,int z)
{
	e[++tot].nxt=head[x];
	head[x]=tot;
	e[tot].to=y;
	e[tot].v=z;
}
int top,cnt,dis[maxn];
vector <int> G[maxn];
void tarjan(int x)
{
	low[x]=dfn[x]=++times;
	instack[++top]=x;
	for(int i=head[x];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(!dfn[to])
		{
			tarjan(to);
			low[x]=min(low[x],low[to]);
		}
		else if(!col[to])
		{
			low[x]=min(low[x],dfn[to]);
		}
	}
	if(low[x]==dfn[x])
	{
		col[x]=++col_num;
		G[col_num].push_back(x);
		while(instack[top]!=x)
		{
			col[instack[top]]=col_num;
			G[col_num].push_back(instack[top]);
			--top;
		}
		--top;
	}
}

void add_edge(int a,int b,int c)
{
	m[++cnt].nxt=fir[a];
	m[cnt].to=b;
	m[cnt].v=c;
	fir[a]=cnt;
}
priority_queue <pair <int,int> > q;
queue <int> que;
void dijkstra()
{
	while(!q.empty())
	{
		int u=q.top().second;
		q.pop(); if(vis[u]) continue;
		vis[u]=1;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int to=e[i].to;int v=e[i].v;
			if(dis[u]<inf && dis[to]>dis[u]+v)
			{
				dis[to]=dis[u]+v;
				q.push(make_pair(-dis[to],to));
			}
		}
		for(int i=fir[u];i;i=m[i].nxt)
		{
			int to=m[i].to;
			if(dis[u]<inf && dis[to]>dis[u]+m[i].v)
				dis[to]=dis[u]+m[i].v;
			in[col[to]]--;
			if(!in[col[to]]) que.push(col[to]);
		}
	}
}
void toposort(int x)
{
	memset(dis,0x3f,sizeof(dis));	
	for(int i=1;i<=col_num;i++)
		if(!in[i]) que.push(i);
	dis[x]=0;
	while(!que.empty())
	{
		int u=que.front();
		que.pop();
		for(int k=0;k<G[u].size();k++)
		{
			int to=G[u][k];
			q.push(make_pair(-dis[to],to));
		}
		dijkstra();
	}
}
int main()
{
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
	scanf("%d%d%d%d",&t,&r,&p,&s);
	int a,b,c;
	for(int i=1;i<=r;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c); add(b,a,c);
	}
	for(int i=1;i<=t;i++)
		if(!dfn[i]) tarjan(i);
	for(int i=1;i<=p;i++)
	{
		scanf("%d%d%d",&a,&b,&c);
		add_edge(a,b,c);
		if(col[a]!=col[b]) in[col[b]]++;
	}
//DAG有负权,求dis
 	toposort(s); 
	for(int i=1;i<=t;i++)
		if(dis[i]==0x3f3f3f3f) printf("NO PATH\n");
		else printf("%d\n",dis[i]);
	return 0;
}

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值