AcWing 341. 最优贸易
题目传送门
一、为什么不能使用 dijkstra 求解本题
由于这题数据过大,如果用 \(dijkstra\) 算法必须要堆优化,而堆优化的前提是每个点只能进堆一次,很明显,这题并不是单纯地把权值累加,是求最大/最小值,所以一个点可能会进多次,与前提矛盾,只能使用 \(SPFA\) 。
二、DFS解法
#include
using namespace std;
const int N = 110000;
const int INF = 0x3f3f3f3f;
vector way_min[N];
vector way_max[N]; //我这里用vector记录数组,大家可以使用邻接矩阵
int w[N]; //每个点的价值
int mins[N], maxs[N]; //一路走来,能获取到的最小买入价值与最大卖出价值
//从1号点出发,一路过来,标记每个点时可以获取到的最小价值
void dfs_min(int step, int v) {
//这里没有if(step==n+1)之类的判断,因为图嘛,有边就派任务,没有边就不派任务了
//走到当前步,携带的最小价值是v,问题是原来算过更小的,此分枝需要剪掉
//这个剪枝剪的漂亮!
if (v >= mins[step]) return; //该点已有更优解,无需再次搜索
//携带过来的最小值,与当前点上的最小值PK,取得包括当前点上最小值在内的最小值
v = min(v, w[step]);
mins[step] = v; //更新最优解
//枚举所有边,继续深搜
for (int i = 0; i < way_min[step].size(); i++)
dfs_min(way_min[step][i], v); //遍历相邻的点
}
//这个函数和上一个函数差不多,这里不再赘述
void dfs_max(int step, int v) {
if (v <= maxs[step]) return;
v = max(v, w[step]);
maxs[step] = v;
for (int i = 0; i < way_max[step].size(); i++)
dfs_max(way_max[step][i], v);
}
int main() {
//初始后结果数组
memset(mins, 0x3f, sizeof mins);
memset(maxs, -0x3f, sizeof maxs);
int n, m; //城市的数目和道路的数目
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> w[i]; //点的价值
//如果 z=1,表示这条道路是城市x到城市y之间的单向道路
//如果 z=2,表示这条道路为城市x到城市y之间的双向道路
int x, y, z;
while (m--) {
cin >> x >> y >> z;
way_min[x].push_back(y); //这里表示求最小值所需的路径
//反向建边
/*
这里有意思,必须反向建边,原因是求最大值那条路径,结尾必须是n!
如果我们还是正向建边,那么dfs也可以求出最大值,但这条路径未必可以到达n!
在反向建边的情况下,因为从n出发,必须可以到达n(废话!),也就是在求“经过
n的所有路径中点权值最大是多少”
然后正反所有方案根据点去枚举,找出都到达的点,并且最大值与最小值差最大的
就OK了!
*/
way_max[y].push_back(x); //因为最大值是从n开始(从i到n可转化为从n到i),所以我们把所有路径倒个头
//双向边
if (z == 2) {
way_min[y].push_back(x);
way_max[x].push_back(y);
}
}
dfs_min(1, w[1]); //从1开始遍历最小值
dfs_max(n, w[n]); //从n开始遍历最大值
//每个中间点都可能是答案,枚举每个点,计算最大值与最小值的差
int ans = 0;
for (int i = 1; i <= n; i++) ans = max(ans, maxs[i] - mins[i]);
cout << ans << endl;
return 0;
}
三、SPFA解法
#include
using namespace std;
const int N = 100005;
const int M = 2000005;
int h1[N], h2[N], e[M], ne[M], idx;
//正反建图,传入头数组指针
void add(int *hh, int a, int b) {
e[idx] = b, ne[idx] = hh[a], hh[a] = idx++;
}
int n, m;
//记录最短,最长距离数组
int dmin[N], dmax[N];
//每个节点的价值
int w[N];
//通过传递图的头数组指针和最短最长距离数组指针,重用spfa算法
void spfa(int *h, int *d, int type) {
queue q;
bool st[N];
if (type) { //求max
q.push(n);
d[n] = w[n];
st[n] = true;
} else { //求min
q.push(1);
d[1] = w[1];
st[1] = true;
}
while (q.size()) {
int u = q.front();
q.pop();
st[u] = false;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (type) {
int dist = max(w[j], d[u]);
if (d[j] < dist) {
d[j] = dist;
if (!st[j]) {
q.push(j);
st[j] = true;
}
}
} else {
int dist = min(w[j], d[u]);
if (d[j] > dist) {
d[j] = dist;
if (!st[j]) {
q.push(j);
st[j] = true;
}
}
}
}
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> w[i];
memset(h1, -1, sizeof h1);
memset(h2, -1, sizeof h2);
int x, y, z;
while (m--) {
cin >> x >> y >> z;
add(h1, x, y), add(h2, y, x);
if (z == 2) add(h1, y, x), add(h2, x, y);
}
//距离初始化
memset(dmin, 0x3f, sizeof dmin);
memset(dmax, -0x3f, sizeof dmax);
//从1出发跑最短路
spfa(h1, dmin, 0);
//从n出发跑最长路
spfa(h2, dmax, 1);
//两边一起来找碰撞点
int res = 0;
for (int i = 1; i <= n; i++) res = max(res, dmax[i] - dmin[i]);
printf("%d\n", res);
return 0;
}