[bzoj2407]探险——重构图+最短路

题目大意:

给定一个无向图,每一条边正着和反着都有一个边权,求一条不经过重复边的路径,使得边权和最小。

思路:

这个题目的思路比较巧妙
网络上的题解只有做法,没有详细地解释。
考虑最暴力的方法,对于1号点能够直接到达的每一个点,把它和1号点的边删掉以后,以这个点为源点跑最短路。
但是这样会T飞。
不妨先放下不能重复走的限制,建立一个新的虚拟汇点t=n+1,然后将所有连向1的边转化成连向t的边。然后对于1到t跑最短路算法
这样子做显然有一些点问题,有的路径可能刚刚才走出去就又回来了,这样是不合法的。
例如一条边(1,v,w),在转移中可能会是这个样子的 \(1->v->t\)
那么如果我们将(1,v,w)给去掉,将可能从(1,v,w)这条边出发的路径转化为一些边(1,u,dis[1...u]) (1,v,w)\(\in\) this path
使得这些u存在于所有\(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;
}

posted @ 2018-10-27 14:31  ylsoi  阅读(200)  评论(0编辑  收藏  举报