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