• NOIP2009提高组 | 最优贸易


    Description

      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 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。

    Input

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

    Output

      包含1 个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出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

    对于 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。
     
     

     
     
    法一:
    分析可知我们只关心在哪里买,在哪里卖,而不关心如何到达这两个点
    因此找到能到起点且能到终点的点,在这样的点里面
    找费用最小和最大的分别就是买入点和卖出点
     
    标记点用BFS,找到终点的点建反图即可
    法一清晰易懂
    #include<cstdio>
    #include<iostream>
    #include<vector>
    #include<queue>
    #include<cstring>
    using namespace std;
    const int maxn = 100005, maxm = 500005, inf = 0x3f3f3f3f;
    
    vector<int> g[maxn], bg[maxn];
    void add(int x, int y) {
        g[x].push_back(y);
        bg[y].push_back(x);
    }
    int w[maxn];
    
    void bfs(int s, int* vis, vector<int> *G) {
        queue<int> Q;
        Q.push(s);
        memset(vis, 0, sizeof(vis));
        
        while(!Q.empty()) {
            int u=Q.front(); Q.pop();
            for(int i=0; i<G[u].size(); i++) {
                int v = G[u][i];
                if(!vis[v]) vis[v] = 1, Q.push(v);
            }
        }
    }
    
    int r1[maxn], r2[maxn];
    int main() {
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i=1; i<=n; i++) scanf("%d", &w[i]);
        while(m--) {
            int a, b, p;
            scanf("%d%d%d", &a, &b, &p);
            add(a, b);
            if(p-1) add(b, a);
        }
        bfs(1, r1, g);
        bfs(n, r2, bg);
        int ans1 = inf, ans2 = 0;
        for(int i=1; i<=n; i++) {
            if(r1[i] && r2[i])
                ans1 = min(ans1, w[i]),
                ans2 = max(ans2, w[i]);
        }
        printf("%d", ans2 - ans1);
        return 0;
    }
    View Code

    法二:洛谷上看到的做法,思路有点奇特

    分层图+SPFA

    考虑某个点 i ,它买入或者卖出水晶球的花费是v[i] 。

    当我们进行买入操作,我们就建立一条有向边转移到一张新图上,边的大小为-v[i],指向点i所能到达的点(在第二层图上)而这张新图就是我们的第二层图。

    它表示:假如我选择走了这条边,就是我在这个点买了这个水晶球,我不会反悔,并且我接下来考虑在某个点卖它。

    当我们进行卖出操作,我们建立一条有向边转移到第三层图上,边的大小为v[i],指向i所能到达的点(在第三层图上)。

    它表示:假如我选择走了这条边,就是我在这个点卖了这个水晶球,我不会反悔,并且我接下来考虑走向终点。

    可以发现,从第一层图走到第二层图走到第三层图走到终点,这就是一个合法的选择,而且分层图把所有可能的决策都考虑到了。

    最后走向终点,我们有两种合法的操作:

    • 不买卖直接走向终点

    直接在第一层图的n号节点建立边权为0的有向边接入一个“超级终点”

    • 买卖一次后走向终点

    在第三层图的n号节点建立边权为0的有向边接入“超级终点”

    最后解释一下为什么我们要分层

    因为当你分了层,你就可以从还未买入这个状态,转移到已经买入准备卖出这个状态,然后在转移到从卖出点走向终点的状态。由于有向边的建立,你不能从第二/三层走回第一层图,这保证了你只做一次买卖,而不是无限做买卖,符合了题目的要求

    而我们最终的答案,就是求从第一层图的1号点,经过三层图走到“超级终点”的最长路,如图所示。

    (小声bb:实测这种做法空间时间都是法一的两倍左右,因此权当一种思维拓展)

    附上自己实现的丑陋代码:

    #include<cstdio>
    #include<iostream>
    #include<vector>
    #include<queue>
    #include<cstring>
    using namespace std;
    const int maxn = 100005, maxm = 500005, inf = 0x3f3f3f3f;
    
    int n;
    struct edge{ int to, w; };
    vector<edge> g[maxn*3];
    int w[maxn];
    void add(int x, int y, int z) {
        g[x].push_back((edge){y, 0});
        g[x+n].push_back((edge){y+n, 0});
        g[x+n*2].push_back((edge){y+n*2, 0});
        g[x].push_back((edge){y+n, -w[x]});
        g[x+n].push_back((edge){y+n*2, w[x]});
    }
    int dis[maxn*3];
    int inq[maxn*3];
    void bfs(int s) {
        queue<int> Q;
        Q.push(s); inq[s] = 1;
        for(int i=1; i<=n*3+1; i++) dis[i] = -inf;
        dis[s] = 0;
        
        while(!Q.empty()) {
            int u=Q.front(); Q.pop();
            inq[u] = 0;
            for(int i=0; i<g[u].size(); i++) {
                int v = g[u][i].to, p = g[u][i].w;
                if(dis[v] < dis[u] + p) {
                    dis[v] = dis[u] + p;
                    if(!inq[v]) Q.push(v), inq[v] = 1;
                }
            }
        }
    }
    
    int main() {
        int m;
        scanf("%d%d", &n, &m);
        g[n].push_back((edge){n*3+1, 0});
        g[n*3].push_back((edge){n*3+1, 0});
        for(int i=1; i<=n; i++)
            scanf("%d", &w[i]);
        while(m--) {
            int a, b, p;
            scanf("%d%d%d", &a, &b, &p);
            add(a, b, p);
            if(p-1) add(b, a, p);
        }
        bfs(1);
        printf("%d", dis[n*3+1]);
        return 0;
    }
    View Code
  • 相关阅读:
    解决windows 下Java编译和运行版本不一致的错误has been compiled by a more recent version
    解决两个OpenCV 报错 (raise.c and GTK) ,重新安装和编译
    Java|如何使用“Java”爬取电话号码(转载)
    Java手机号码工具类(判断运营商、获取归属地)以及简要的原理跟踪(转载)
    Caused by: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
    mysql分库分表
    python将html文件转换为pdf
    mysql获取字段名和对应的注释
    mysql大表查询慢对应方案
    harbor安装
  • 原文地址:https://www.cnblogs.com/de-compass/p/11872695.html
Copyright © 2020-2023  润新知