【NOIP2009提高组】最优贸易

我只是一个虾纸丫 提交于 2019-12-04 15:59:55

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
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!