7201. 「JOI 2020 Final」奥运公交

Description&Data Constraint

7201. 「JOI 2020 Final」奥运公交

对于全部数据,\(2\le N \le 200,1\le M \le 5\times 10^4,1\le U_i,V_i\le N,U_i\not= V_i,0\le C_i\le 10^6,0\le D_i\le 10^9\)。

Solution

首先明确一个性质,翻转的边需要在最短路树上,否则对答案没有贡献。

首先求出 \(1\) 到 \(i\),\(i\) 到 \(n\) 的最短路径,并把在最短路树上的边打上标记。

也求出 \(n\) 到 \(i\),\(i\) 到 \(1\) 的最短路径,同时打上最短路树的标记。

具体操作可以建正图和反图共 \(4\) 个,两个正图,两个反图,正图和反图中各有一个以 \(1\) 为起点,一个以 \(n\) 为起点,以不同的起点求出对应的最短路。

约定:

  1. 以 \(1\) 为起点的正图为图 \(1\)。目的是求出 \(1\) 到 \(i\) 的最短路。

  2. 以 \(n\) 为起点的反图为图 \(2\)。目的是求出 \(i\) 到 \(n\) 的最短路。

  3. 以 \(n\) 为起点的正图为图 \(3\)。目的是求出 \(n\) 到 \(i\) 的最短路。

  4. 以 \(1\) 为起点的反图为图 \(4\)。目的是求出 \(i\) 到 \(1\) 的最短路。

  5. 一条边的起点是 \(x\),终点是 \(y\),权值是 \(v\)。

令初始答案为在不翻转边下的 \(1\) 到 \(n\) 和 \(n\) 到 \(1\) 的最短路径和。

考虑枚举边,对于 \(1\) 到 \(n\),如果该边不在图的最短路树上,就返回原值,否则将此边翻转跑最短路。

对于翻转,显然不能找到原边将其翻转。有一种想法是给原边打上标记,加入新的翻转边并在最短路结束后将其删掉。

另一种想法是手动走翻转边。对于一条边,要么走要么不走。不走就是在没有原边的基础上跑最短路,走则是先从 \(1\) 走到 \(y\),再从 \(x\) 走到 \(n\),同时补上中间少了的 \(v\)。

将 \(1\) 到 \(n\) 和 \(n\) 到 \(1\) 分开处理,求出两个最短路,判断最短路径和加上修改当前边的权值与答案的大小,更新答案。

最后判断 \(-1\),输出答案即可。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 1e9
#define N 205
#define M 50005
#define ll long long
using namespace std;
int n,m,x[M],y[M];
ll ans,z[M],d[M];
struct gra
{
	int s,tot,cant,last[M];
	ll dis1[N],dis2[N];
	bool bin[M],bj[N];
	struct node
	{
		int to,next,head;
		ll val;
	}a[M];
	void add(int x,int y,ll z)
	{
		a[++tot].to=y;
		a[tot].val=z;
		a[tot].next=a[x].head;
		a[x].head=tot;
	}
	void dij()
	{
		memset(bj,false,sizeof(bj));
		for (int i=1;i<=n+1;++i)
			dis1[i]=inf;
		dis1[s]=0;
		for (int i=1;i<=n;++i)
		{
			int k=n+1;
			for (int j=1;j<=n;++j)
				if (!bj[j]&&dis1[j]<dis1[k]) k=j;
			if (k==n+1) break;
			bj[k]=true;
			for (int j=a[k].head;j;j=a[j].next)
			{
				int u=a[j].to;
				if (!bj[u]&&dis1[u]>dis1[k]+a[j].val) dis1[u]=dis1[k]+a[j].val,last[u]=j;
			}
		}
		for (int i=1;i<=n;++i)
			if (i!=s) bin[last[i]]=true;
	}
	void get()
	{
		memset(bj,false,sizeof(bj));
		for (int i=1;i<=n+1;++i)
			dis2[i]=inf;
		dis2[s]=0;
		for (int i=1;i<=n;++i)
		{
			int k=n+1;
			for (int j=1;j<=n;++j)
				if (!bj[j]&&dis2[j]<dis2[k]) k=j;
			if (k==n+1) break;
			bj[k]=true;
			for (int j=a[k].head;j;j=a[j].next)
			{
				int u=a[j].to;
				if (!bj[u]&&dis2[u]>dis2[k]+a[j].val&&j!=cant) dis2[u]=dis2[k]+a[j].val;
			}
		}
	}
	ll calc(int e,int ss)
	{
		if (!bin[e]) return dis1[ss];
		cant=e;
		get();
		return dis2[ss];
	}
}g[10];
int main()
{
	scanf("%d%d",&n,&m);
	g[1].s=g[4].s=1;
	g[2].s=g[3].s=n;
	for (int i=1;i<=m;++i)
	{
		scanf("%d%d%lld%lld",&x[i],&y[i],&z[i],&d[i]);
		g[1].add(x[i],y[i],z[i]);g[2].add(y[i],x[i],z[i]);
		g[3].add(x[i],y[i],z[i]);g[4].add(y[i],x[i],z[i]);
	}
	for (int i=1;i<=4;++i)
		g[i].dij();
	ans=g[1].dis1[n]+g[3].dis1[1];
	for (int i=1;i<=m;++i)
	{
		ll d1=min(g[1].calc(i,n),g[1].calc(i,y[i])+z[i]+g[2].calc(i,x[i]));
		ll d2=min(g[3].calc(i,1),g[3].calc(i,y[i])+z[i]+g[4].calc(i,x[i]));
		ans=min(ans,d1+d2+d[i]);
	}
	if (ans>=1e9) printf("-1\n");
	else printf("%lld\n",ans);
	return 0;
}
上一篇:redis数据库-django操作redis


下一篇:P6881 [JOI 2020 Final] 火事 题解