ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【题解】P1073 [NOIP2009 提高组] 最优贸易(图论,最短路,SPFA)

2022-05-22 08:00:07  阅读:118  来源: 互联网

标签:dis1 dis2 题解 路径 NOIP2009 SPFA 上点权 节点


【题解】P1073 [NOIP2009 提高组] 最优贸易

此题妙哉!

虽然说这只是道绿题,但是还是卡了我好久,主要是这道题里面有两个思想我完全想不到。我太菜了

写一下思路来记录一下这道绝妙的题目吧。


题目链接

[NOIP2009 提高组] 最优贸易 - 洛谷

题意概述

有一张 \(n\) 个点 \(m\) 条边的图,其中有些边是双向边,有些边是单向边。每个点 \(i\) 都有一个点权 \(p_i\),要求在这张图上找到一条从 1 到 \(n\) 的路径,使路径上能经过两个点 \(s,t\)(先经过 \(s\) 再经过 \(t\)),并且 \(p_t-p_s\) 最大。

思路分析

首先,

要求在这张图上找到一条从 1 到 \(n\) 的路径,使路径上能经过两个点 \(s,t\)(先经过 \(s\) 再经过 \(t\)),并且 \(p_t-p_s\) 最大。

这个条件可以转化为:

有一个中继节点 \(k\),是 1 到 \(n\) 的路径上一点,同时,\(s\) 是 \(1-k\) 路径上点权最小的点,\(t\) 是 \(k-n\) 路径上点权最大的点

那么这一点对我们做题有什么启发呢?

我们可以发现,假如我们找到了以下三个,那么这个问题就解决了:

  1. 中继节点 \(k\);

  2. \(1-k\) 路径上点权最小的点;

  3. \(k-n\) 路径上点权最大的点。

分步来看!

中继节点 \(k\)

可以发现,由于 \(1-n\) 有多条路径,所以除了 1 和 \(n\) 之外的所有点都可能成为中继节点(废话),那么我们枚举这个中继节点好了。

\(1-k\) 路径上点权最小的点

我们知道,SPFA 可以求最短路,那么它能不能求路径上点权最小的点呢?

实际上完全可以。

仿照 SPFA 求最短路的做法,我们定义 \(dis1_i\) 表示的是从节点 1 到节点 \(i\) 的所有路径中,能够经过的权值最小的节点的权值。(这就是本题的第一个重要思想)

\(dis1_i\) 的计算方法与单源最短路径的计算方法类似,只需要把 \(dis1_x+w(x,y)\) 更新 \(dis1_y\) 改成 \(\min(dis1_x,p_y)\) 更新 \(dis1_y\) 即可。

所以我们只需要以 1 为起点跑一遍这样的 SPFA 即可求出 \(1-n\) 路径上以任何一个其它的点为中继节点得到的点权最小的点。

\(k-n\) 路径上点权最大的点

显然我们可以仿照上面的做法,用 \(dis2_i\) 表示从节点 \(i\) 到 \(n\) 的所有路径中,能够经过的权值最大的节点的权值。然后更新也可以类比上面。

但是有一个问题。

我们应该以谁为起点跑 SPFA 呢?

以 \(x\) 为起点吗?显然不对啊。\(x\) 都不确定。

那么什么是确定的呢?终点 \(n\) 啊。

所以就用到了本题的第二个重要思想——建反图

我们可以建立一张反图:在原图基础上把所有边的方向取反后得到的图。

然后再从 \(n\) 为起点跑一遍 SPFA。从而求出所有的 \(dis2_i\)。

最后,枚举每个中继节点 \(i\),答案即为所有 \(i\) 中 \(dis2_i-dis1_i\) 的最小值。

于是这道题目就完美解决!

求解步骤

  1. 对于输入的所有边,用两个 basic_string 分别作为正反图存边的信息。(注意双向边正反都要存一遍)

  2. 以 \(i\) 为起点在原图上跑一遍“最短点权“的 SPFA,求出所有 \(dis1_i\)。

  3. 以 \(n\) 为起点在反图上跑一遍“最短点权”的 SPFA,求出所有 \(dis2_i\)。

  4. 枚举每个 \(i\),则 \(ans=\min(dis2_i-dis1_i)\)。

关键点

  1. 题意转化;

  2. 想到将点权转化为边权来跑 SPFA;

  3. 想到建立反图;

其中 2 3 也是本题最重要的两个思想。

易错点

  1. 思路上,此题不能用 dijkstra 来求,因为不满足 dijkstra 的贪心性质;

  2. 代码实现上,记得除 1 外的 \(dis1\) 开始要初始化为 INF,1 的 \(dis1\) 初始化为 \(p_1\);

    而除 \(n\) 外所有的 \(dis2\) 要初始化为 0;\(n\) 的 \(dis2\) 初始化为 \(p_n\)。

代码实现

//luoguP1073
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<string>
using namespace std;
const int maxn=1e5+10;
int n,m;
int p[maxn],dis1[maxn],dis2[maxn],ans;

basic_string<int>e1[maxn];
basic_string<int>e2[maxn];

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

void SPFA()
{
	queue<int>q;
	q.push(1);
	for(int i=2;i<=n;i++)dis1[i]=(1ll<<29)-1;
	dis1[1]=p[1];
    while(!q.empty())
    {
    	int x=q.front();
    	q.pop();
    	for(int y:e1[x])
    	{
    		if(dis1[y]>min(dis1[x],p[y]))
    		{
    			dis1[y]=min(dis1[x],p[y]);
    			q.push(y);
			}
		}
	}
	while(!q.empty())q.pop();
	q.push(n);
	dis2[n]=p[n];
	while(!q.empty())
    {
    	int x=q.front();
    	//cout<<x<<endl;
    	q.pop();
    	for(int y:e2[x])
    	{
    		//cout<<y<<endl;
    		//cout<<"ex "<<dis2[x]<<" "<<p[y]<<endl;
    		if(dis2[y]<max(dis2[x],p[y]))
    		{
    			dis2[y]=max(dis2[x],p[y]);
    			q.push(y);
			}
		}
	 } 
}

int main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++)p[i]=read();
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		x=read();y=read();z=read();
		if(z==1){e1[x]+=y;e2[y]+=x;}
		else
		{
			e2[x]+=y;e2[y]+=x;
			e1[x]+=y;e1[y]+=x;
		 } 
	}
	//for(int i=1;i<=n;i++)cout<<e1[i].size()<<" "<<e2[i].size()<<endl;
	SPFA();
	for(int i=1;i<=n;i++)
	{
		//cout<<dis2[i]<<" "<<dis1[i]<<endl;
		ans=max(ans,dis2[i]-dis1[i]);
	}
	cout<<ans<<endl;
} 
/*两个 basic_string,两次 SPFA。 
定义 dis1[i] 表示的是从 1 走到 i 的所有路径中能够经过的权值最小的节点的权值。
dis2[i] 表示的是从 i 走到 n 的所有路径中能够经过的权值最大的节点的权值
那么答案就为 min(dis2[i]-dis1[i]);
破点为边,在点权上做 SPFA。
做两次,分别求得 dis1[i] 和 dis2[i] 即可。
妙哉!
*/ 

标签:dis1,dis2,题解,路径,NOIP2009,SPFA,上点权,节点
来源: https://www.cnblogs.com/xrkforces/p/luogu-P1073.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有