Statement
Solve
5pts
暴力枚举更改边,然后直接上 \(dijkstra\)
考虑到堆优化的复杂度是 \((n+m)\log n\) ,而不优化是 \(n^2\)
所以在本题中,我们应不加堆,那么总复杂度为 \(O(n^2m)\)
100pts
考虑对我们刚刚的暴力进行优化
发现对复杂度影响最大的其实是 \(m\)
发现我们在每次枚举边,修改边,重新计算的过程中
我们可以先求出 \(SPT\) ,即最短路径生成树
Dijkstra算法:每一个点的最短路都是有另外一个点更新的;
假设将i的最短路更新节点记为:pre[i]
让整个图只保留<pre[i], i>,那么就是一棵树;
这棵树被称为最短路径树(Shortest Path Tree)
对于一条边:
-
不在最短路径生成树中:\(O(1)\)
- 就是原最短路
- 必须经过该边,但路径上的其他边都必须是在最短路径树上
-
在最短路径生成树中:\(O(n^2)\)
翻转后,重新计算即可
显然, \(SPT\) 上只有 \(O(n)\) 条边
那么,这样的复杂度就是 \(O(n^3+m)\)
考虑具体实现,我们知道从 \(i\to 1\) 和从 \(1\to i\) 不是一回事
为了 \(O(1)\) 地解决边不在 \(SPT\) 中的情况,我们需要四颗 \(SPT\) (代码
- \(g[0].dis[i]\) 表示 \(1\to i\)
- \(g[1].dis[i]\) 表示 \(i\to n\)
- \(g[2].dis[i]\) 表示 \(i\to 1\)
- \(g[3].dis[i]\) 表示 \(n\to i\)
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf = 1e18;
const int N = 205;
const int M = 5e4+5;
int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
return s*w;
}
int n,m,ans;
int x[M],y[M],c[M],d[M];
struct Graph{
struct Edge{
int nex,to,dis;
}edge[M];
int head[N],pre[N],d1[N],d2[N];
//pre[i] 最短路径树父边;d1 未翻转;d2 翻转某条边
bool exist[M],vis[N];//exist[i] 边 i 是否在 spt 中
int st,sp,elen;//sp 被翻转的边
void add(int u,int v,int w){
edge[++elen]={head[u],v,w};
head[u]=elen;
}
void dijkstra1(){
for(int i=1;i<=n+1;++i)d1[i]=inf;//注意这里到n+1
memset(vis,0,sizeof(vis)),d1[st]=0;
for(int i=1;i<=n;++i){
int u=n+1;
for(int j=1;j<=n;++j)
if(!vis[j]&&d1[j]<d1[u])u=j;
if(u==n+1)break;vis[u]=1;
for(int e=head[u],v;e;e=edge[e].nex)
if(!vis[v=edge[e].to]&&d1[v]>d1[u]+edge[e].dis)
d1[v]=d1[u]+edge[e].dis,pre[v]=e;
}
for(int i=1;i<=n;++i)if(i!=st)exist[pre[i]]=1;
}
void dijkstra2(){
for(int i=1;i<=n+1;++i)d2[i]=inf;
memset(vis,0,sizeof(vis)),d2[st]=0;
for(int i=1;i<=n;++i){
int u=n+1;
for(int j=1;j<=n;++j)
if(!vis[j]&&d2[j]<d2[u])u=j;
if(u==n+1)break;vis[u]=1;
for(int e=head[u],v;e;e=edge[e].nex)
if(!vis[v=edge[e].to]&&sp!=e&&d2[v]>d2[u]+edge[e].dis)
d2[v]=d2[u]+edge[e].dis;
//这里将翻转实现为不进行松弛,因为显然即使松弛,对答案没有影响
}
}
int calc(int e,int pos){//翻转边 e,问到点 pos 的最短路
if(exist[e]){//在 spt 中
if(sp!=e)sp=e,dijkstra2();//重新算
return d2[pos];
}
else return d1[pos];
}
}g[4];
signed main(){
n=read(),m=read();
g[0].st=g[2].st=1,g[1].st=g[3].st=n;
for(int i=1;i<=m;++i)
x[i]=read(),y[i]=read(),c[i]=read(),d[i]=read(),
g[0].add(x[i],y[i],c[i]),g[1].add(y[i],x[i],c[i]),
g[2].add(y[i],x[i],c[i]),g[3].add(x[i],y[i],c[i]);
for(int i=0;i<4;++i)g[i].dijkstra1();
ans=g[0].d1[n]+g[3].d1[1];//不翻转
for(int i=1;i<=m;++i)
ans=min(ans,min(g[0].calc(i,n),g[0].calc(i,y[i])+c[i]+g[1].calc(i,x[i]))+d[i]+
min(g[3].calc(i,1),g[3].calc(i,y[i])+c[i]+g[2].calc(i,x[i])));
//min(ans,min(1->n,1->v->u->n)+翻转费用+min(n->1,n->v->u->1))
printf("%lld\n",ans<1e17?ans:-1);
return 0;
}