[LOJ3255][JOI 2020 Final]奥运公交(最短路)
[LOJ3255][JOI 2020 Final]奥运公交(最短路)
题面
给出一个\(n\)个点\(m\)条边的有向图,经过每条边需要费用\(c_i\).选择一条边并将其反向需要费用\(d_i\)(反向后经过的费用不变).问至多反向一条边,从1到n再回到1的最小花费
\(n \leq 200,m \leq 50000\)
分析
考虑枚举反向的边\(i=(u,v)\),经过的费用为\(w\),反转的费用为\(c\).记\(d(u,v)\)表示\(u\)到\(v\)的最少费用,\(f(i,u,v)\)表示不经过边\(i\)从\(u\)到\(v\)的最短路
那么
这是因为从\(1\)到\(n\)有2种选择:不经过\((u,v)\)直接走到\(n\), 或者先到\(v\),经过\((v,u)\)再到\(n\). 从\(n\)到\(1\)的情况同理。总费用为\(d(1,n)+d(n,1)+c\)
那么我们考虑如何求出\(f\).我们发现起点和终点只会是\(1\)或\(n\).不妨考虑起点为1的情况。求出\(\operatorname{dist}(i)\)表示原图起点到\(i\)的费用,并求出一棵最短路树\(T\)
对于第一种情况直接开始时预处理即可。第二种情况需要重新跑一次最短路。注意到最短路树上只有\(n-1\)条边,第二种情况会出现\(O(n)\)次。如果用堆优化的Dijkstra,单次复杂度为\(O((n+m)\log n)\),因为\(m\)达到了\(n^2\)级别,总复杂度\(O((n^2+nm)\log n)=O(n^3\log n)\),,需要较强的常数优化才能通过。但是无堆优化的Dijkstra复杂度是\(O(n^2+m)\)且常数很小,总复杂度\(O(n^3)\)可以通过本题。
其他的\(f\)同理可求:\(f(i,u,n)\)从\(n\)出发在原图的反图上跑最短路,\(f(i,n,u)\)从\(n\)出发在原图上跑最短路,\(f(i,u,1)\)从\(1\)出发在原图的反图上跑最短路. 代码可复用的部分较多,注意封装。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define maxm 50000
#define maxn 200
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long ll;
int n,m;
struct Graph{
int S;
struct edge{
int from;
int to;
int len;
int cost;
int next;
}E[maxm+5];
int head[maxn+5];
int esz;
void add_edge(int u,int v,int w,int c){
esz++;
E[esz].from=u;
E[esz].to=v;
E[esz].len=w;
E[esz].cost=c;
E[esz].next=head[u];
head[u]=esz;
}
ll dist1[maxn+5];
int last[maxn+5];
bool on_tree[maxm+5];
void dijkstra(){//O(n^2)的Dijkstra,排除m的影响
static bool vis[maxn+5];
memset(dist1,0x3f,sizeof(dist1));
memset(vis,0,sizeof(vis));
dist1[S]=0;
for(int p=1;p<=n;p++){
int x=-1;
for(int i=1;i<=n;i++) if(!vis[i]&&(x==-1||dist1[i]<dist1[x])) x=i;
if(x==-1) break;
vis[x]=1;
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(dist1[y]>dist1[x]+E[i].len){
dist1[y]=dist1[x]+E[i].len;
last[y]=i;
}
}
}
for(int i=1;i<=n;i++) if(i!=S) on_tree[last[i]]=1;
}
ll dist2[maxn+5];
void dijkstra2(int bane){
static bool vis[maxn+5];
memset(dist2,0x3f,sizeof(dist2));
memset(vis,0,sizeof(vis));
dist2[S]=0;
for(int p=1;p<=n;p++){
int x=-1;
for(int i=1;i<=n;i++) if(!vis[i]&&(x==-1||dist2[i]<dist2[x])) x=i;
if(x==-1) break;
vis[x]=1;
for(int i=head[x];i;i=E[i].next){
int y=E[i].to;
if(i==bane) continue;//由于有重边,不能用端点来判断
if(dist2[y]>dist2[x]+E[i].len) dist2[y]=dist2[x]+E[i].len;
}
}
}
ll calc(int eid,int x){//不经过边i的1到x最短路
if(!on_tree[eid]) return dist1[x];
else{//重新跑一遍最短路
dijkstra2(eid);
return dist2[x];
}
}
}G[4];
int main(){
static int u[maxm+5],v[maxm+5],w[maxm+5],c[maxm+5];
scanf("%d %d",&n,&m);
G[0].S=1;G[1].S=n;G[2].S=1;G[3].S=n;
for(int i=1;i<=m;i++){
scanf("%d %d %d %d",&u[i],&v[i],&w[i],&c[i]);
G[0].add_edge(u[i],v[i],w[i],c[i]);
G[1].add_edge(v[i],u[i],w[i],c[i]);//反图上n的最短路树,对应正图上i到n的路径
G[2].add_edge(v[i],u[i],w[i],c[i]);
G[3].add_edge(u[i],v[i],w[i],c[i]);
}
for(int i=0;i<4;i++) G[i].dijkstra();
ll ans=G[0].dist1[n]+G[3].dist1[1];
for(int i=1;i<=m;i++){
ll d1=min(G[0].calc(i,n)/*1直接绕过i到n*/,G[0].calc(i,v[i])/*1->v*/+w[i]+G[1].calc(i,u[i])/*u->n*/);
ll d2=min(G[3].calc(i,1)/*n直接绕过i到1*/,G[3].calc(i,v[i])/*n->v*/+w[i]+G[2].calc(i,u[i])/*u->1*/);
if(d1>=INF||d2>=INF) continue;
ans=min(ans,d1+d2+c[i]);
}
if(ans>=INF) ans=-1;
printf("%lld\n",ans);
}