Description
C国有n个大城市和m条道路,每条道路连接这n个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这m条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1条。
C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到C国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设C国n个城市的标号从1~ n,阿龙决定从1号城市出发,并最终在 n号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有n个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品――水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来C国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
假设C国有5个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。
Input
第一行包含2个正整数n和m,中间用一个空格隔开,分别表示城市的数目和道路的数目。
第二行n个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这n个城市的商品价格。
接下来m行,每行有3个正整数x,y,z,每两个整数之间用一个空格隔开。如果z=1,表示这条道路是城市x到城市y之间的单向道路;如果z=2,表示这条道路为城市x和城市y之间的双向道路。
Output
一 个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出0。
Sample Input 1
5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2
Sample Output 1
5
Hint
输入数据保证1号城市可以到达n号城市。
对于 10%的数据,1≤n≤6。
对于 30%的数据,1≤n≤100。
对于 50%的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。
对于 100%的数据,1≤n≤100000,1≤m≤500000,1≤x,y≤n,1≤z≤2,1≤各城市。
水晶球价格≤100。
分析:本题要求从唯一起点走到唯一终点,寻找一路上遇到水晶球价格最小值和最大值的最优解,即两者能在一条路上遇到且后者减之差最大,而且必须先遇到最小值再遇到最大值。这道题并不关心具体在哪遇到最值,只关心路上遇到了最值,且按理想次序遇到。
可以利用SPFA算法从起点开始走,走的过程进行松弛操作,更新从开始到到达一个结点这段时间内,所遇到的水晶球价格最小值;再建一张反图,从终点开始走,走的过程进行松弛操作,更新反向行走时,从终点到到达一个结点这段时间内,所遇到的水晶球价格最大值。两次行走可以用两个数组记录水晶球价格最值的更新,两个数组内的更新元素数应该和结点数相同。最后,枚举任意一个起点到终点的中间结点,计算在到达此中间结点及之后遇到的水晶球价格最大值与在到达此中间结点及之前遇到的水晶球价格的最小值之差,找到它们中的最大值,即为答案。
另外,写这道题时我遇到一个坑,我给存储水晶球价格最值的数组分别赋初值为0x7FFFFFFF和0,0x7FFFFFFF和-0x7FFFFFFF,0x7F7F7F7F和0都不能AC,收获了PA或WA,只有赋值为0x7F7F7F7F和-0x7F7F7F7F时才能AC,记住这个玄乎的坑,以后给整型变量赋最值初值就用(-)0x7F7F7F7F。
接下来分析已AC代码。
#include <stdio.h>
#include <memory.h>
#include <queue>
#include <algorithm>
struct EDGE{
int v,next;//到达结点和下一条边
};
int head_1[100010],cnt_1=0;//图一,求最小价格
int head_2[100010],cnt_2=0;//图二,求最大价格
EDGE edge_1[1000010];//考虑最极端的情况,都是双向边
EDGE edge_2[1000010];//故最多有500000*2条单向边需要存储
int d_1[100010];//记录最小价格的更新
int d_2[100010];//记录最大价格的更新
bool visit_1[100010]={
0};//记录结点是否入队
bool visit_2[100010]={
0};
int price[100010];//存储各城市的水晶球价格
int n,m,x,y,z;
int ans=0;//保存最终答案
//链式前向星给图一加边
inline void add_edge_1(int x,int y)
{
edge_1[++cnt_1].v=y;
edge_1[cnt_1].next=head_1[x];
head_1[x]=cnt_1;
}
//链式前向星给图二加边
inline void add_edge_2(int x,int y)
{
edge_2[++cnt_2].v=y;
edge_2[cnt_2].next=head_2[x];
head_2[x]=cnt_2;
}
//走图一找最小价格
void spfa_1()
{
std::queue<int> que;
que.push(1);//起点入队
visit_1[1]=true;
d_1[1]=price[1];//在起点,遇到过的最小价格只能是起点处的价格
int now;//记录出队结点
while(!que.empty())
{
now=que.front();
que.pop();
visit_1[now]= false;
//更新从当前结点走到邻接结点路上遇到过的最小价格
for(int i=head_1[now];~i;i=edge_1[i].next)
{
//如果到此邻接结点及之前遇到过的最小价格,
//大于此地价格和到当前结点及之前遇到过的最小价格中的最小值
if(d_1[edge_1[i].v]>std::min(price[edge_1[i].v],d_1[now]))
{
//则此邻接结点的记录需要更新
d_1[edge_1[i].v]=std::min(price[edge_1[i].v],d_1[now]);
if(!visit_1[edge_1[i].v]){
//如果此邻接结点没入队则入队
que.push(edge_1[i].v);
visit_1[edge_1[i].v]=true;
}
}
}
}
}
//走图二找最大价格
void spfa_2()
{
std::queue<int> que;
que.push(n);//终点入队
visit_2[n]=true;
d_2[n]=price[n];//在终点(反图起点),遇到过的最大价格只能是该地价格
int now;//记录出队结点
while(!que.empty())
{
now=que.front();
que.pop();
visit_2[now]= false;
//更新从当前结点走到邻接结点路上遇到过的最大价格
for(int i=head_2[now];~i;i=edge_2[i].next)
{
//如果到此邻接结点及之前遇到过的最大价格,
//小于此地价格和到当前结点及之前遇到过的最大价格中的最大值
if(d_2[edge_2[i].v]<std::max(price[edge_2[i].v],d_2[now]))
{
//则此邻接结点的记录需要更新
d_2[edge_2[i].v]=std::max(price[edge_2[i].v],d_2[now]);
if(!visit_2[edge_2[i].v]){
//如果此邻接结点没入队则入队
que.push(edge_2[i].v);
visit_2[edge_2[i].v]=true;
}
}
}
}
}
int main()
{
memset(head_1,-1,sizeof(head_1));//初始化
memset(head_2,-1,sizeof(head_2));//-1表示边表结束
memset(d_1,0x7f7f7f7f,sizeof(d_1));//求最小值赋初值为最大值
memset(d_2,-0x7f7f7f7f,sizeof(d_2));//求最大值赋初值为最小值
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d",&price[i]);
}
for(int i=1;i<=m;++i)
{
scanf("%d %d %d",&x,&y,&z);
add_edge_1(x,y);//每在原图加一条边
add_edge_2(y,x);//就要在反图加一条反边
if(z==2){
//双向边则再加一次反边
add_edge_1(y,x);
add_edge_2(x,y);
}
}
spfa_1();//走图,找最值
spfa_2();
for(int i=1;i<=n;i++)//寻找路上最大差价
{
ans=std::max(ans,d_2[i]-d_1[i]);
}
printf("%d",ans);
return 0;
}