1. 程式人生 > 其它 >2019 ICPC 銀川 H. Delivery Route (強連通分量+拓撲+分塊最短路)

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;	
}