1. 程式人生 > >[bzoj2407]探險——重構圖+最短路 dalaos' blogs Some Links

[bzoj2407]探險——重構圖+最短路 dalaos' blogs Some Links

題目大意:

給定一個無向圖,每一條邊正著和反著都有一個邊權,求一條不經過重複邊的路徑,使得邊權和最小。

思路:

這個題目的思路比較巧妙
網路上的題解只有做法,沒有詳細地解釋。
考慮最暴力的方法,對於1號點能夠直接到達的每一個點,把它和1號點的邊刪掉以後,以這個點為源點跑最短路。
但是這樣會T飛。
不妨先放下不能重複走的限制,建立一個新的虛擬匯點t=n+1,然後將所有連向1的邊轉化成連向t的邊。然後對於1到t跑最短路演算法
這樣子做顯然有一些點問題,有的路徑可能剛剛才走出去就又回來了,這樣是不合法的。
例如一條邊(1,v,w),在轉移中可能會是這個樣子的 1

> v > t 1->v->t
那麼如果我們將(1,v,w)給去掉,將可能從(1,v,w)這條邊出發的路徑轉化為一些邊(1,u,dis[1…u]) (1,v,w)
\in
this path
使得這些u存在於所有 1 > t 1->t 的路徑上,並且對於所有的u,都有1到u的最短路的第一個點不是v。
這樣以後我們再去求最短路,便不用擔心一條邊跑出去再跑回去的問題,因為有了上面的限制,通過這條邊出去之後再回去一定不是最短的。
慮怎麼建立上面所說的等效邊,可以對於原圖做一次最短路,記錄每個點的最短路徑的第一個點f[u],列舉每一條邊(u,v,w),如果f[u]!=f[v],那麼則說明這是f[u]和f[v]的範圍的分界處,在這裡分別對f[u]和f[v]都建立等效邊即可。
當然如果u=1或者v=1的情況需要特殊考慮,但是方法是類似的。具體方法可以參考hzwer的部落格。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define pii pair<int,int>
#define fi first
#define se second
#define mk make_pair
typedef long long ll;

using namespace std;

void File(){
	freopen("bzoj2407.in","r",stdin);
	freopen("bzoj2407.out","w",stdout);
}

template<typename T>void read(T &_){
	T __=0,mul=1; char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')mul=-1;
		ch=getchar();
	}
	while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
	_=__*mul;
}

const int maxn=1e4+10;
const int maxm=2e5+10;
int n,m,dis[maxn],fr[maxn];
int beg[maxn],from[maxm<<1],to[maxm<<1],las[maxm<<1],w[maxm<<1],cnte=1;
priority_queue< pii,vector<pii>,greater<pii> >qu;

void add(int u,int v,int val){
	las[++cnte]=beg[u]; beg[u]=cnte; to[cnte]=v; from[cnte]=u; w[cnte]=val;
}

void Dijkstra(){
	memset(dis,63,sizeof(dis));
	memset(fr,0,sizeof(fr));
	dis[1]=0; qu.push(mk(0,1));
	while(!qu.empty()){
		int u=qu.top().se,d=qu.top().fi;
		qu.pop();
		if(d!=dis[u])continue;
		for(int i=beg[u];i;i=las[i]){
			int v=to[i];
			if(d+w[i]<dis[v]){
				fr[v]= u==1 ? i/2 : fr[u];
				dis[v]=d+w[i];
				qu.push(mk(dis[v],v));
			}
		}
	}
}

void rebuild(){
	int sz=cnte; cnte=0;
	memset(beg,0,sizeof(beg));
	REP(i,2,sz){
		if(from[i]==1){
			if(fr[to[i]]!=i/2)
				add(from[i],to[i],w[i]);
		}
		else if(to[i]==1){
			if(fr[from[i]]==i/2)
				add(from[i],n+1,w[i]);
			else add(1,n+1,dis[from[i]]+w[i]);
		}
		else{
			if(fr[from[i]]==fr[to[i]])
				add(from[i],to[i],w[i]);
			else add(1,to[i],dis[from[i]]+w[i]);
		}
	}
}

int main(){
	File();
	read(n); read(m);
	int u,v,val0,val1;
	REP(i,1,m){
		read(u),read(v),read(val0),read(val1);
		add(u,v,val0),add(v,u,val1);
	}
	Dijkstra();
	rebuild();
	Dijkstra();
	printf("%d\n",dis[n+1]);
	return 0;
}