【题解】P1073 [NOIP2009 提高组] 最优贸易(图论,最短路,SPFA)
【题解】P1073 [NOIP2009 提高组] 最优贸易
此题妙哉!
虽然说这只是道绿题,但是还是卡了我好久,主要是这道题里面有两个思想我完全想不到。我太菜了
写一下思路来记录一下这道绝妙的题目吧。
题目链接
题意概述
有一张
思路分析
首先,
要求在这张图上找到一条从 1 到
的路径,使路径上能经过两个点 (先经过 再经过 ),并且 最大。
这个条件可以转化为:
有一个中继节点
那么这一点对我们做题有什么启发呢?
我们可以发现,假如我们找到了以下三个,那么这个问题就解决了:
-
中继节点
; -
路径上点权最小的点; -
路径上点权最大的点。
分步来看!
中继节点
可以发现,由于
我们知道,SPFA 可以求最短路,那么它能不能求路径上点权最小的点呢?
实际上完全可以。
仿照 SPFA 求最短路的做法,我们定义
所以我们只需要以 1 为起点跑一遍这样的 SPFA 即可求出
显然我们可以仿照上面的做法,用
但是有一个问题。
我们应该以谁为起点跑 SPFA 呢?
以
那么什么是确定的呢?终点
所以就用到了本题的第二个重要思想——建反图。
我们可以建立一张反图:在原图基础上把所有边的方向取反后得到的图。
然后再从
最后,枚举每个中继节点
于是这道题目就完美解决!
求解步骤
-
对于输入的所有边,用两个 basic_string 分别作为正反图存边的信息。(注意双向边正反都要存一遍)
-
以
为起点在原图上跑一遍“最短点权“的 SPFA,求出所有 。 -
以
为起点在反图上跑一遍“最短点权”的 SPFA,求出所有 。 -
枚举每个
,则 。
关键点
-
题意转化;
-
想到将点权转化为边权来跑 SPFA;
-
想到建立反图;
其中 2 3 也是本题最重要的两个思想。
易错点
-
思路上,此题不能用 dijkstra 来求,因为不满足 dijkstra 的贪心性质;
-
代码实现上,记得除 1 外的
开始要初始化为 INF,1 的 初始化为 ;而除
外所有的 要初始化为 0; 的 初始化为 。
代码实现
//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] 即可。
妙哉!
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现