zoukankan      html  css  js  c++  java
  • 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没必要多说。

    这道题是道好题。

  • 相关阅读:
    总结Selenium自动化测试方法(二)测试环境搭建
    画画学习
    喜欢看的电影
    angular学习知识点
    前端编辑器
    托尔斯泰经典语录:没有风暴,船帆不过是一块破布
    当下最流行的10大H5前端框架
    移动端问题总纲
    第二阶段团队冲刺04
    第二阶段团队冲刺03
  • 原文地址:https://www.cnblogs.com/fusiwei/p/11206959.html
Copyright © 2011-2022 走看看