NOIP 2009 最优贸易

洛谷 P1073 最优贸易

https://www.luogu.org/problemnew/show/P1073

JDOJ 1649: [NOIP2009]最优贸易 T3

https://neooj.com:8082/oldoj/problem.php?id=1649

题目描述

  C 国有n 个大城市和m 条道路,每条道路连接这n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1 条。
  C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
  商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设C 国n 个城市的标号从1~ n,阿龙决定从1 号城市出发,并最终在n 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有n 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
  假设 C 国有5 个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。



  假设 1~n 号城市的水晶球价格分别为4,3,5,6,1。
  阿龙可以选择如下一条线路:1->2->3->5,并在2 号城市以3 的价格买入水晶球,在3号城市以5 的价格卖出水晶球,赚取的旅费数为2。
  阿龙也可以选择如下一条线路 1->4->5->4->5,并在第1 次到达5 号城市时以1 的价格买入水晶球,在第2 次到达4 号城市时以6 的价格卖出水晶球,赚取的旅费数为5。
  现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。

输入

  输入文件第一行包含 2 个正整数n 和m,中间用一个空格隔开,分别表示城市的数目和道路的数目。
  第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这n 个城市的商品价格。
  接下来 m 行,每行有3 个正整数,x,y,z,每两个整数之间用一个空格隔开。如果z=1,表示这条道路是城市x 到城市y 之间的单向道路;如果z=2,表示这条道路为城市x 和城市y 之间的双向道路。

输出

  输出共1 行,包含1 个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出0。

样例输入

5 5 4 3 5 6 1 1 2 1 1 4 1 2 3 2 3 5 1 4 5 2

样例输出

5

提示

 

【数据范围】
  输入数据保证 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。

 
 图论。
题目大意是这样:
要求在一个带点权的图中找到一条从1到N的路径,使得路径上能选出p,q两个点,使得两者差最大。
无向边视为两条有向边。
肯定是最短路算法了,floyd肯定不行,dijkstra算法如果加堆优化的话勉强能过(但是我不会),SPFA比较稳当。
有些 可爱的同学可能会问:怎么能是最短路呢?这道题明明跟最短最长啥关系没有啊!
要学会转化,我们想在路径上找到一个权值最小的点,再找一个权值最大的点,可以在原算法基础上更改数组的意义,原来的f数组f[i]表示从单源点到i点的最短路,我们可以把f数组改成f[i]表示从1到i的所有路径中,权值最小的点的权值。
然后考虑最大点权值。
很简单,反向建图,从终点往起点跑,再开一个数组,d[i]表示从n-i的所有路径中权值最大的那个。
跑完两遍SPFA,就可以处理出两个数组f,d,最后枚举所有节点进行更新答案即可。
 
看似很简单,实际代码实现有几个坑。
 
第一:我们怎么更新f数组和d数组。
第二:更新的时候(包括更新答案)用min,还是max,条件为>时更新,还是条件<时更新?
第三:初值如何设置?
 
先上代码,我们满满解析:
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
int n,m;
int a[100001];
int tot,to[1000001],nxt[1000001],head[100001];
int tot1,to1[1000001],nxt1[1000001],head1[100001];
int f[100001],v[100001],d[100001];
void add(int x,int y)
{
    to[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
}
void add1(int x,int y)
{
    to1[++tot1]=y;
    nxt1[tot1]=head1[x];
    head1[x]=tot1;
}
void spfa()
{
    memset(d,0x3f,sizeof(d));
    memset(v,0,sizeof(v));
    queue<int> q;
    q.push(1);v[1]=1;
    while(!q.empty())
    {
        int x,y;
        x=q.front();
        q.pop();
        v[x]=0;
        for(int i=head[x];i;i=nxt[i])
            if(d[y=to[i]]>min(d[x],a[y]))
            {
                d[y]=min(d[x],a[y]);
                if(v[y]==0)
                    q.push(y),v[y]=1;
            }
    }
}
void spfa1()
{
    memset(f,0,sizeof(f));
    memset(v,0,sizeof(v));
    queue<int> q;
    q.push(n);v[n]=1;
    while(!q.empty())
    {
        int x,y;
        x=q.front();
        q.pop();
        v[x]=0;
        for(int i=head1[x];i;i=nxt1[i])
            if(f[y=to1[i]]<max(f[x],a[y]))
            {
                f[y]=max(f[x],a[y]);
                if(v[y]==0)
                    q.push(y),v[y]=1;
            }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        if(z==1)
            add(x,y),add1(y,x);
        if(z==2)
        {
            add(x,y),add(y,x);
            add1(x,y),add1(y,x);
        }
    }
    spfa();
    spfa1();
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(f[i]-d[i],ans);
    printf("%d",ans);
    return 0;
}

首先第一个问题和第二个问题。

在spfa函数中,我们已经预处理好了点权数组a,在枚举x节点的所有出边时,如果这个d[y]要大于d[x]和a[y]的较小值,就应该更新。(不需要再解释了吧)

f[i]数组同理。

第三个问题,初值。

我们要想能更新d数组,(因为d数组表示的是小值),要把d数组预处理成最大。

f数组当然是最小。

这里要注意,与模板spfa不一样的是,f和d的源点都不需要重新设置值,因为表示的是路径上最小值(最大值),在源点时的值就是a[源点],自然没有必要影响整体的程序。

ans置成0没必要多说。

这道题是道好题。

posted @ 2019-07-18 14:09  Seaway-Fu  阅读(250)  评论(0编辑  收藏  举报