luogu2149 [SDOI2009]Elaxia的路线

链接

  https://www.luogu.org/problem/show?pid=2149

题解

  一开始用一种错误的方法AC了,后来看了题解,又写了正解。

  这种spfa+dp的题一般都很神奇。我们可以把所有同时存在于两条最短路上的路径染成红色,然后查询最长红色链。

  可以先以x1 y1 x2 y2分别为起点跑一遍最短路,然后一条边在最短路上存在的条件就是这个点的两个端点到x1 y1的距离和加上本身的长度等于最短路(x2 y2同理)。把同时存在于两个点对的最短路上的边染成红色,然后求一下最长的红色链就行了(这个可以用一个dp解决)。

  如何证明红色链连起来是最短路的一部分?

  我们考虑两条有一个公共端点的红链,任何情况都可以化简为如下所示情形。


  假设a-b-f和c-d-e分别是两条s到t的最短路,b和d是我要合并的两条红链。那么我们要证明的是,存在一条同时包含b和d的最短路。由前提条件可知a-b-f和c-d-e都是最短路,所以a+b=c,d+e=f(否则最短路将会发生变动)。所以两条红链合并之后的最短路就是a-b-d-e。

代码

//最短路+dp
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define inf 0x3f3f3f3f
#define maxn 1700
#define maxm 1000000
#define abs(x) (x<0?-x:x)
using namespace std;
int dist[maxn][maxn], N, M, head[maxm], to[maxm], w[maxm], next[maxm], tot, in[maxm],
	red[maxm], x1, y1, x2, y2, q[maxn<<1], f[maxm];
void adde(int a, int b, int v){to[++tot]=b;w[tot]=v;next[tot]=head[a];head[a]=tot;}
void spfa(int *dist, int S)
{
	int x, i, p, l=N, r=N;
	for(i=1;i<=N;i++)dist[i]=inf;
	dist[S]=0;
	in[S]=1;
	q[r++]=S;
	while(l<r)
	{
		in[x=q[l++]]=0;
		for(p=head[x];p;p=next[p])
		{
			if(dist[to[p]]>dist[x]+w[p])
			{
				dist[to[p]]=dist[x]+w[p];
				if(!in[to[p]])
				{
					in[to[p]]=1;
					if(dist[to[p]]<dist[q[l]])q[--l]=to[p];
					else q[r++]=to[p];
				}
			}
		}
	}
}
void init()
{
	int u, v, w, i;
	scanf("%d%d%d%d%d%d",&N,&M,&x1,&y1,&x2,&y2);
	for(i=1;i<=M;i++)scanf("%d%d%d",&u,&v,&w),adde(u,v,w),adde(v,u,w);
}
void work()
{
	int d1, d2, i, p, x, ans=0;
	queue<int> q;
	spfa(dist[x1],x1);
	spfa(dist[x2],x2);
	spfa(dist[y1],y1);
	spfa(dist[y2],y2);
	d1=dist[x1][y1], d2=dist[x2][y2];
	for(i=1;i<=N;i++)for(p=head[i];p;p=next[p])
	{
		if(min(dist[y1][i]+dist[x1][to[p]],dist[x1][i]+dist[y1][to[p]])+w[p]==d1
		   and min(dist[y2][i]+dist[x2][to[p]],dist[x2][i]+dist[y2][to[p]])+w[p]==d2
		   and dist[x1][i]<=dist[x1][to[p]])
			red[p]=true;
	}
	for(i=1;i<=tot;i++)
		if(red[i])in[i]=1,q.push(i),f[i]=w[i],ans=max(ans,f[i]);
	while(!q.empty())
	{
		in[x=q.front()]=0;q.pop();
		for(p=head[to[x]];p;p=next[p])
		{
			if(!red[p])continue;
			if(f[x]+w[p]>f[p])
			{
				f[p]=f[x]+w[p];
				ans=max(ans,f[p]);
				if(!in[p])q.push(p);
			}
		}
	}
	printf("%d",ans);
}
int main()
{
	init();
	work();
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值