2019 ICPC 银川 H. Delivery Route (强连通分量+拓扑+分块最短路)

题链:https://nanti.jisuanke.com/t/42388

题意:一个有向图,有双向边(边权全为正),单向边(边权可能为负),求一个起点到其他点的最短路。

思路:有负权边且卡spfa。注意题目中有一个非常重要的条件。

也就是环中不可能有负权边。那么,我们考虑tarjan缩点,那么在一个强连通分量中的点之间的边都是正权边,那么内部的最短路就能用Dijkstra了。

缩点后,图变成一个树,强连通分量之间的最短路就能用Topo排序求了。

注意,分是指一个强连通分量(一个缩点),一个强连通分量的处理。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e4+10;
const int M = 2e5+10;
const int inf = 0x3f3f3f3f;
int n,x,y,s;
struct node{
	int to,w,nex;
}g[M];
struct Node{
	int to,d;
	friend bool operator<(Node a,Node b){
		return a.d>b.d;
	} 
};

int cnt,head[N]; 
int dis[N],in[N];

void add(int u,int v,int w){
	g[cnt]=node{v,w,head[u]};
	head[u]=cnt++;
}
int dfn[N],top,id,low[N],sta[N],c[N],cl,vis[N];
void init(){
	cl=top=id=cnt=0;
	for(int i=1;i<=n;i++)
		dfn[i]=in[i]=0,dis[i]=inf,head[i]=-1;
}
vector<int> scc[N];//每个强连通分量中有那些点 
//tarjan求强连通分量 
void tarjan(int u){ 	
	low[u]=dfn[u]=++id; sta[++top]=u; vis[u]=1; 
	for(int i=head[u];~i;i=g[i].nex){
		int v=g[i].to;
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);			
		}
		else if(vis[v])
			low[u]=min(low[u],dfn[v]);				
	}
	if(dfn[u]==low[u]){
		c[u]=++cl;
		scc[cl].push_back(u);
		while(sta[top]!=u){
			c[sta[top]]=cl;
			scc[cl].push_back(sta[top]);
			vis[sta[top--]]=0;
		}
		vis[sta[top--]]=0;	
	}
}
vector<int> ng[N];//缩点后的图,一棵树 
unordered_map<int,unordered_map<int,int> > mp; //标记重边 
vector<int> ok[N];//强连通分量可以作起点(与另一强连通分量中的点相连的点,有可能更新最短路)的点 
//建新图,用来Topo和分块处理 
void getng(){
	tarjan(s);
	for(int u=1;u<=n;u++){
		if(!c[u]) continue;
		for(int i=head[u];~i;i=g[i].nex){
			int v=g[i].to;
			if(!c[v]||c[u]==c[v]) continue;
			ok[c[v]].push_back(v);
			if(mp[c[u]][c[v]]) continue;
			in[c[v]]++;
			mp[c[u]][c[v]]=1;
			ng[c[u]].push_back(c[v]);
		}
	}	
}
bool book[N];
//Dijkstra求强连通分量内部的最短路 
void dijs(int st){
	priority_queue<Node> pq;
	for(int i=1;i<=n;i++)
		book[i]=0;
	pq.push(Node{st,dis[st]});
	while(!pq.empty()){
		Node now = pq.top();
		pq.pop();
		int u=now.to;
		if(book[u]) continue;
		book[u]=1;
		for(int i=head[u];~i;i=g[i].nex){
			int v=g[i].to;
			if(c[u]!=c[v]) continue;
			if(dis[v]>dis[u]+g[i].w){
				dis[v]=dis[u]+g[i].w;
				pq.push(Node{v,dis[v]});
			}
		}		
	}
}
//Topo排序求强连通分量之间的最短路 
queue<int> q; 
void topo(int st){
	int len = scc[st].size();
	for(int j=0;j<len;j++){//此强连通分量中的所有点 
		int u=scc[st][j];
		for(int i=head[u];~i;i=g[i].nex){//更新其他强连通分量中的点 
			int v=g[i].to;	
			if(c[u]==c[v]) continue;
			dis[v]=min(dis[v],dis[u]+g[i].w);
		}
	}
	len=ng[st].size();
	for(int i=0;i<len;i++){//Topo排序 
		int v=ng[st][i];
		in[v]--;
		if(in[v]==0){
			int siz=ok[v].size();
			for(int j=0;j<siz;j++)
				q.push(ok[v][j]);
		}
	}	
}

int main(void){
	scanf("%d%d%d%d",&n,&x,&y,&s);
	init();
	for(int i=1;i<=x;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	}
	for(int i=1;i<=y;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
	}
	getng();
	dis[s]=0;
	q.push(s);
	while(!q.empty()){
		int u =q.front();
		q.pop();
		dijs(u);
		if(q.empty()||c[q.front()]!=c[u])//此强连通分量都已处理完 
			topo(c[u]);
	}
	for(int i=1;i<=n;i++)
		if(dis[i]==inf)
			puts("NO PATH");
		else 
			printf("%d\n",dis[i]);
	return 0;	
} 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值