#3255. 「JOI 2020 Final」奥运公交

Description&Data Constraint

在这里插入图片描述

对于全部数据, 2 ≤ N ≤ 200 , 1 ≤ M ≤ 5 × 1 0 4 , 1 ≤ U i , V i ≤ N , U i ≠ V i , 0 ≤ C i ≤ 1 0 6 , 0 ≤ D i ≤ 1 0 9 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 2N200,1M5×104,1Ui,ViN,Ui=Vi,0Ci106,0Di109

Solution

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

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

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

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

约定:

  1. 1 1 1 为起点的正图为图 1 1 1。目的是求出 1 1 1 i i i 的最短路。

  2. n n n 为起点的反图为图 2 2 2。目的是求出 i i i n n n 的最短路。

  3. n n n 为起点的正图为图 3 3 3。目的是求出 n n n i i i 的最短路。

  4. 1 1 1 为起点的反图为图 4 4 4。目的是求出 i i i 1 1 1 的最短路。

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

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

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

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

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

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

最后判断 − 1 -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;
}
c(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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值