Description
C国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。
任意两个城市之间最多只有一条道路直接相连。
这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。
C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。
但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到 C国旅游。
当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。
设 C国 n 个城市的标号从 1 ∼ n,阿龙决定从 1 号城市出发,并最终在 n 号城市结束自己的旅行。
在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。
阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。
因为阿龙主要是来 C国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。
请你告诉阿龙,他最多能赚取多少旅费。
Input
第一行 2 个正整数 n 和 m ( 1 ≤ n ≤ 100000 , 1 ≤ m ≤ 500000 ),中间用一个空格隔开,分别表示城市的数目和道路的数目。
第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这 n 个城市的商品价格 , 1 ≤ 各城市水晶球价格 ≤ 100 。
接下来 m 行,每行有 3 个正整数,x, y, z,每两个整数之间用一个空格隔开。
如果 z = 1,表示这条道路是城市 x 到城市 y 之间的单向道路;如果 z = 2,表示这条道路为城市 x 和城市 y 之间的双向道路。
Output
一个整数,表示答案
Sample Input
5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2Sample Output
5
发现这道题如果会强连通分量的话用强连通分量解题并不难
如果没有学过的话建议先去b站看视频学完再回来看
思路:用强连通分量的话只需要按照一般流程缩点建新图,再利用拓扑序递推出答案即可
需要注意一些小细节:因为人物是从结点1出发,所以从结点1所在连通块开始递推,而不是从所有入度为0的连通块开始递推
以下是普通tarjan算法代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 200, M = 5 * 1e5 + 20;
int n, m;
///各结点的价格,走到当前连通块的最大获利,走到当前连通块时遇到的最小价格
int price[N], ans[N], dist[N];
//连通块数量,结点所在的连通块,各连通块的最大价格,各连通块的最小价格,各连通块的入度
int scc_cnt, id[N], ma[N], mi[N], in[N];
int dfn[N], low[N], time_;
int stk[N], in_stk[N], top; //栈,记录是否入栈,栈顶
//h1用于建原图,h2用于建新图,考虑双向边所以开2*M,新旧两个图所以开2*2*M
int h1[N], h2[N], e[4 * M], ne[4 * M], idx;
void add1(int a, int b) {e[idx] = b, ne[idx] = h1[a], h1[a] = idx++;}
void add2(int a, int b) {e[idx] = b, ne[idx] = h2[a], h2[a] = idx++;}
void trajan(int u) {
dfn[u] = low[u] = ++time_;
stk[++top] = u;
in_stk[u] = 1;
for (int i = h1[u]; i != -1; i = ne[i]) {
int j = e[i];
if (!dfn[j]) {
trajan(j);
low[u] = min(low[j], low[u]);
}
else if (in_stk[j])
low[u] = min(low[j], low[u]);
}
if (low[u] == dfn[u]) {
int y;
scc_cnt++;
ma[scc_cnt] = -0x3f3f3f3f, mi[scc_cnt] = 0x3f3f3f3f;
do {
y = stk[top--];
id[y] = scc_cnt;
in_stk[y] = 0;
ma[scc_cnt] = max(ma[scc_cnt], price[y]);
mi[scc_cnt] = min(mi[scc_cnt], price[y]);
} while (y != u);
}
}
void topsort() {
//因为从结点1出发,因此只将结点1所在的连通块入队
int q[N], tt = -1, hh = 0;
q[++tt] = id[1]; dist[id[1]] = mi[id[1]];//dist[]用于记录人物走到当前结点时遇到的最小价格
while (hh <= tt) {
int t = q[hh++];
for (int i = h2[t]; i != -1; i = ne[i]) {
int j = e[i];
dist[j] = min(dist[t], mi[j]);
ans[j] = max(ans[t], ma[j] - dist[j]); //ans[]记录人物走到当前连通块时的最大获利
if (--in[j] == 0)q[++tt] = j;
}
}
}
int main() {
memset(h1, -1, sizeof h1);
memset(h2, -1, sizeof h2);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)scanf("%d", price + i);
int a, b, z;
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &a, &b, &z);
add1(a, b);
if (z == 2)add1(b, a);
}
//正常缩点
for (int i = 1; i <= n; i++)if (!dfn[i])trajan(i);
//正常建新图
for (int i = 1; i <= n; i++)
for (int j = h1[i]; j != -1; j = ne[j]) {
int k = e[j];
if (id[i] == id[k])continue;
in[id[k]]++;
add2(id[i], id[k]);
}
//通过拓扑序递推答案
topsort();
printf("%d\n", ans[id[n]]);//输出结点所在连通块的最大获利即可
return 0;
}
注意:上面这份代码在很多平台都可以ac,但是遇到测试数据较强的平台会出现wa,原因是测试数据较强会导致tarjan在递归时栈溢出,遇到这种情况建议先用并查集缩点一次,减小点规模后再跑一遍tarjan即可ac(或者用spfa算法解这道题目)
以下是先用并查集缩点再跑一遍tarjan的代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 200, M = 5 * 1e5 + 20;
int n, m;
///各结点的价格,走到当前连通块的最大获利,走到当前连通块时遇到的最小价格
int price[N], ans[N], dist[N];
//连通块数量,结点所在的连通块,各连通块的最大价格,各连通块的最小价格,各连通块的入度
int scc_cnt, id[N], ma[N], mi[N], in[N];
int dfn[N], low[N], time_;
int stk[N], in_stk[N], top; //栈,记录是否入栈,栈顶
//h1用于建原图,h2用于建新图,考虑双向边所以开2*M,新旧两个图所以开2*2*M
int h1[N], h2[N], e[4 * M], ne[4 * M], idx;
void add1(int a, int b) { e[idx] = b, ne[idx] = h1[a], h1[a] = idx++; }
void add2(int a, int b) { e[idx] = b, ne[idx] = h2[a], h2[a] = idx++; }
vector<pair<int, int> > edge;//存储单向边
int per[N];
int find(int x) {
if (x == per[x])return x;
return per[x] = find(per[x]);
}
void trajan(int u) {
dfn[u] = low[u] = ++time_;
stk[++top] = u;
in_stk[u] = 1;
for (int i = h1[u]; i != -1; i = ne[i]) {
int j = e[i];
if (!dfn[j]) {
trajan(j);
low[u] = min(low[j], low[u]);
}
else if (in_stk[j])
low[u] = min(low[j], low[u]);
}
if (low[u] == dfn[u]) {
int y;
scc_cnt++;
do {
y = stk[top--];
id[y] = u;
in_stk[y] = 0;
ma[u] = max(ma[u], ma[y]); //更新缩点后该连通块的最大最小价格
mi[u] = min(mi[u], mi[y]);
per[y] = u; //这里利用并查集将连通块缩点到u
} while (y != u);
}
}
void topsort() {
//因为从结点1出发,因此只将结点1所在的连通块入队
queue<int>q;
int p1 = find(1);
q.push(p1); dist[p1] = mi[p1];//dist[]用于记录人物走到当前结点时遇到的最小价格
ans[p1] = ma[p1] - mi[p1];
while (q.size()) {
int t = q.front(); q.pop();
for (int i = h2[t]; i != -1; i = ne[i]) {
int j = e[i];
dist[j] = min(dist[t], mi[j]);
ans[j] = max(ans[t], ma[j] - dist[j]); //ans[]记录人物走到当前连通块时的最大获利
if (--in[j] == 0)q.push(j);
}
}
}
int main() {
memset(h1, -1, sizeof h1);
memset(h2, -1, sizeof h2);
memset(mi, 0x3f, sizeof mi);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) { scanf("%d", price + i); per[i] = i; }
int a, b, z;
//先用并查集缩点
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &a, &b, &z);
if (z == 1)edge.push_back({ a,b });//存储单向边
else per[find(a)] = find(b);//如果是双向边则合并为同一连通块
}
//建并查集缩点后的图
for (int i = 0; i < edge.size(); i++) {
int a = edge[i].first, b = edge[i].second;
if (find(a) != find(b))
add1(find(a), find(b));
}
//更新并查集各连通块的最大最小价值
for (int i = 1; i <= n; i++) {
int a = find(i);
ma[a] = max(ma[a], price[i]);
mi[a] = min(mi[a], price[i]);
}
//将并查集缩点后的连通块跑一遍tarjan缩点
for (int i = 1; i <= n; i++)if (!dfn[find(i)])trajan(find(i));
//建新图
for (int i = 0; i < edge.size(); i++) {
int a = edge[i].first, b = edge[i].second;
int pa = find(a), pb = find(b);
if (pa != pb) {
in[pb]++;
add2(pa, pb);
}
}
//通过拓扑序递推答案
topsort();
printf("%d\n", ans[find(n)]);//输出结点所在连通块的最大获利即可
return 0;
}