题意
给定一个 n n n 个点, m m m 条边的图,每条边有 e e e, p p p, 两个权值。请找出一条从 1 1 1 到 n n n 的最短路,在满足路径上 e e e 的和最小的前提下,使得 p p p 的和最大。输出 m i n e min_e mine 与 m a x p max_p maxp。
数据范围
2
≤
n
≤
1
0
5
,
1
≤
m
≤
3
×
1
0
5
2\leq n\leq10^5,1\leq m\leq3\times10^5
2≤n≤105,1≤m≤3×105
1
≤
u
i
,
v
i
≤
n
,
0
≤
e
i
,
p
i
≤
1
0
9
1\leq u_i,v_i\leq n,0\leq e_i,p_i\leq10^9
1≤ui,vi≤n,0≤ei,pi≤109
错误做法
首先说一个典型错误做法:我们要求满足最短路条件下的 p p p 的和最大,那么考虑在在更新最短路的时候直接维护一个存最大值的数组。 d i s t dist dist 数组维护最短路有, d p dp dp 数组维护最短路上的和的最大值,核心代码如下:
void dijkstra() {
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> q;
q.push({0, 1});
while (q.size()) {
PII tt = q.top();
int d = tt.first;
int v = tt.second;
q.pop();
if (st[v]) continue;
st[v] = 1;
for (int i = 0; i < g[v].size(); i ++) {
node tmp = g[v][i];
int to = tmp.to, e = tmp.e, p = tmp.p;
if (dist[to] > dist[v] + e) {
dp[to] = max(dp[to], dp[v] + p);
dist[to] = dist[v] + e;
q.push({dist[to], to});
} else if (dist[to] == dist[v] + e) {
dp[to] = max(dp[to], dp[v] + p);
}
}
}
}
错误原因: d i j k s t r a dijkstra dijkstra 算法不能求最长路,也不能把边权转换为负数求最短路。这两者等价,不满足该算法的贪心的性质。具体证明可以看这篇博客:经典Dijkstra与最长路
题解
首先在图上以
e
e
e 为边权跑一边最短路,得到一张最短路图。枚举所有边,只要满足
d
i
s
t
[
v
]
=
d
i
s
t
[
u
]
+
e
dist[v] = dist[u]+e
dist[v]=dist[u]+e,即起始点到
u
u
u 的最短距离加上
e
e
e 等于 起始点到
v
v
v 的最短距离,则该边一定属于某条最短路,也一定属于最短路图。
建好最短路图后,考虑在图上求
p
p
p 的最长路。
我们可以发现,
e
i
e_i
ei 与
p
i
p_i
pi 可以等于
0
0
0。当存在一个环,环上所有的边权都是
0
0
0,该环被称为零环。当图内存在环时,我们考虑使用
t
a
r
j
a
n
tarjan
tarjan 缩点,得到
D
A
G
DAG
DAG 后,再在图上使用拓扑排序来求最长路。
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10, inf = 0x3f3f3f3f3f3f3f3f;
typedef pair<int, int> PII;
struct node {
int to, e, p;
};
int n, m, dist[N], st[N], ok[N];
vector<vector<node>> g(N), g2(N);
vector<vector<PII>> G(N);
int dfn[N], low[N], timestamp;
int id[N], scc_cnt, _size[N], in_stk[N], in[N];
stack<int> stk;
int res, dp[N];
void init(int n) {
for (int i = 1; i <= n; i ++) {
g[i].clear();
g2[i].clear();
G[i].clear();
ok[i] = id[i] = dfn[i] = low[i] = _size[i] = in_stk[i] = 0;
in[i] = dp[i] = 0;
}
timestamp = 0;
scc_cnt = 0;
while (stk.size()) stk.pop();
res = 0;
}
void dijkstra() {
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> q;
q.push({0, 1});
while (q.size()) {
PII tt = q.top();
int d = tt.first;
int v = tt.second;
q.pop();
if (st[v]) continue;
st[v] = 1;
for (int i = 0; i < g[v].size(); i ++) {
node tmp = g[v][i];
int to = tmp.to, e = tmp.e, p = tmp.p;
if (dist[to] > dist[v] + e) {
dist[to] = dist[v] + e;
q.push({dist[to], to});
}
}
}
}
void tarjan(int u) {
dfn[u] = low[u] = ++ timestamp;
stk.push(u); in_stk[u] = true;
for (int i = 0; i < g2[u].size(); i ++) {
node tmp = g2[u][i];
int to = tmp.to, e = tmp.e, p = tmp.p;
if (!dfn[to]) {
tarjan(to);
low[u] = min(low[u], low[to]);
} else if (in_stk[to]) low[u] = min(low[u], low[to]);
}
if (dfn[u] == low[u]) {
++ scc_cnt;
int y;
do {
y = stk.top();
in_stk[y] = false;
stk.pop();
id[y] = scc_cnt;
_size[scc_cnt] ++;
} while (y != u);
}
}
void topsort() {
dp[id[1]] = 0;
queue<int> q;
for (int i = 1; i <= scc_cnt; i ++)
if (!in[i])
q.push(i);
while (q.size()) {
int u = q.front();
q.pop();
for (int i = 0; i < G[u].size(); i ++) {
int to = G[u][i].first, p = G[u][i].second;
dp[to] = max(dp[to], dp[u] + p);
in[to] --;
if (!in[to])
q.push(to);
}
}
}
void slv() {
cin >> n >> m;
init(n);
while (m --) {
int u, v, e, p;
cin >> u >> v >> e >> p;
g[u].push_back({v, e, p});
}
dijkstra();
for(int i = 1; i <= n; i ++) {
for (int j = 0; j < g[i].size(); j ++) {
node tmp = g[i][j];
int to = tmp.to, e = tmp.e, p = tmp.p;
if (dist[i] + e == dist[to]) {
g2[i].push_back({to, e, p});
}
}
}
for (int i = 1; i <= n; i ++)
if (!dfn[i] && g2[i].size())
tarjan(i);
for(int i = 1; i <= n; i ++) {
for (int j = 0; j < g2[i].size(); j ++) {
node tmp = g2[i][j];
int to = tmp.to, e = tmp.e, p = tmp.p;
if (id[i] != id[to]) {
G[id[i]].push_back({id[to], p});
in[id[to]] ++;
}
}
}
topsort();
cout << dist[n] << ' ' << dp[id[n]] << '\n';
}
signed main() {
cin.tie(0)->sync_with_stdio(0);
int _; cin >> _;
while (_--)
slv();
}