题目描述
C国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。
任意两个城市之间最多只有一条道路直接相连。
这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1条。
C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。
但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到C国旅游。
当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。
设C国 n 个城市的标号从 1~n,阿龙决定从1号城市出发,并最终在 n 号城市结束自己的旅行。
在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。
阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。
因为阿龙主要是来C国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。
请你告诉阿龙,他最多能赚取多少旅费。
样例
输入
5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2
输出
5
算法1
(分层图状态转移 + SPFA) $O(n^2)$
读完这道题,可以发现这样的事实:
你可以在图上任意走动
最终答案只与你的买入与卖出价格有关(我们就把买入卖出价值作为边权)
如果你买入了一个水晶球,你是没有不卖它的道理的(显然咯,买了不卖血亏…)
n平方的算法不难得出:
我只关心我在哪里买了这个水晶球,在哪里把它卖出去,并且,我能否从起点走到我的买入点,从买入点走到卖出点,然后在走到n
因此,先枚举两个点再bfs检查能否到达,然后更新答案。
而此题的难点在与你如何知道你是否能够到达买入,卖出,钟点(即上两行 并且 后面我说的话),和你能否把所有可能的情况考虑在内。
分层图可以很好的解决这个问题。
由于可以任意走动,所以我们可以建一张图,令图上的边全都是0,表示我的走动对我最终的结果没有影响。
考虑某个点 i ,它买入或者卖出水晶球的花费是v[i] 。
那么 当我们进行买入操作,我们就建立一条有向边转移到一张新图上,边的大小为-v[i],指向点i所能到达的点(在第二层图上)而这张新图就是我们的第二层图。
它表示:假如我选择走了这条边,就是我在这个点买了这个水晶球,我不会反悔,并且我接下来考虑在某个点卖它。
当我们进行卖出操作,我们建立一条有向边转移到第三层图上,边的大小为v[i],指向i所能到达的点(在第三层图上)。
它表示:假如我选择走了这条边,就是我在这个点卖了这个水晶球,我不会反悔,并且我接下来考虑走向终点。
可以发现,从第一层图走到第二层图走到第三层图走到终点,这就是一个合法的选择,而且分层图把所有可能的决策都考虑到了。
最后走向终点,我们有两种合法的操作:
不买卖直接走向终点
直接在第一层图的n号节点建立边权为0的有向边接入一个“超级终点”
买卖一次后走向终点
在第三层图的n号节点建立边权为0的有向边接入“超级终点”
最后解释一下为什么我们要分层:
因为当你分了层,你就可以从还未买入这个状态,转移到已经买入准备卖出这个状态,然后在转移到从卖出点走向终点的状态。由于有向边的建立,你不能从第二/三层走回第一层图,这保证了你只做一次买卖,而不是无限做买卖,符合了题目的要求
而我们最终的答案,就是求从第一层图的1号点,经过三层图走到“超级终点”的最长路
C++ 代码
#include<bits/stdc++.h>
using namespace std;
#define oo 1<<18;
const int maxn = 100010;
struct u {
int v,len;
};
int n, m, v[maxn], d[maxn*3+1];
vector<u> G[maxn*3+1];
void addedge(int x,int y) {
G[x].push_back((u){y,0});
G[x+n].push_back((u){y+n,0});
G[x+2*n].push_back((u){y+2*n,0});
G[x].push_back((u){y+n,-v[x]});
G[x+n].push_back((u){y+2*n,v[x]});
return;
}
queue<int> Q;
bool inq[maxn*3+1];
void spfa() {
for(int i = 1;i <= n;i++) d[i] = -oo;
d[1] = 0;
inq[1] = true;
Q.push(1);
while(!Q.empty()) {
int tp = Q.front(); Q.pop();
inq[tp] = false;
int len = G[tp].size();
for(int i = 0;i < len;i++) {
u x = G[tp][i];
if(d[x.v] < d[tp] + x.len) {
d[x.v] = d[tp] + x.len;
if(inq[x.v] == false) {
Q.push(x.v);
inq[x.v] = true;
}
}
}
}
}
int main() {
cin >> n >> m;
for(int i = 1;i <= n;i++) cin >> v[i];
for(int i = 1,x,y,z;i <= m;i++) {
cin >> x >> y >> z;
addedge(x,y);
if(z == 2) addedge(y,x);
}
G[n].push_back((u){3*n+1,0});
G[n*3].push_back((u){3*n+1,0});
n = 3*n + 1;
spfa();
cout << d[n] << endl;
return 0;
}
大佬 想问下这题如果想卡能不能卡掉spfa
一般来说用 $ O(m * log_2(n + m)) $ 复杂度的堆优化$ Dijkstra $比较保险
是用 O(m∗log2(n+m))O(m∗log2(n+m)) 复杂度的堆优化DijkstraDijkstra比较保险,谢谢大佬
不是啊,这SPFA并不是$ O(n^2) $的算法(在出题人不卡的时候),SPFA一般是$ O(m*k) $ 其中 $ k $ 是一个常数。
是n的平方
“n平方的算法不难得出”,恐怕这个不只是平方,应是立方吧
是平方吧
等等看楼主怎么说的
我也不是很确定