[JOI 2020 Final] 奥运公交 题解

Statement

JOI 2020 Final オリンピックバス - 洛谷

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

参考 题解 P6880 - Time_tears 的博客

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

posted @ 2021-08-23 17:26  _Famiglistimo  阅读(66)  评论(0编辑  收藏  举报