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