29 S2模拟赛T2 高速公路升级 题解
高速公路升级
题面
给定一张 \(n\) 个点,\(m\) 条边的有向图,每条边有边权 \(t_i\) ,以及下降指数 \(w_i\)。
\(q\) 次询问,每次给定一个 \(k\),表示有 \(k\) 次给给边升级的机会,每次给一条边升级可以使边权 \(t_i\) 减小 \(w_i\),问从 1 到 n 的最短路。
\(1 \le n \le 2 \times 10^5, 1 \le m, q \le 6 \times 10^5\)
\(2 \le t_i \le 10^{12}, 1 \le w_i \le \min(t_i - 1, 10^9)\)
保证 \(\forall 1 \le j \le m, t_j - w_j \times k_i > 0\)
题解
这道题怎么解?
首先,我们要想到这道题一个非常重要的结论:对于一条路径,我们如果选择升级,那么一定只会升级这条路径上的某一条边。
为什么呢?
因为题面中有:保证 \(\forall 1 \le j \le m, t_j - w_j \times k_i > 0\)
所以我们将升级的机会全部都用在一条边上也不会让这条边变成 0,所以对于任意一种不是分配在一条边上的升级次数,我们都可以将升级次数都分配在 \(w_i\) 最大的那条边上,一定会使答案减小。
有了这个结论,我们就可以可以选择在每次询问的时候枚举将哪条边升级,然后跑最短路,时间复杂度 \(O(n^3 \log n)\)。
因为只有这条边变化,所以我们可以考虑怎么优化求最短路的过程,一个做法是预处理出每个点到起点和终点的最短路。
然后再每次询问的时候还是枚举每条边,根据预处理出来的值 \(O(1)\) 计算答案,时间复杂度 \(O(n^2)\)。
下面考虑怎么继续优化时间复杂度 ?
我们想想刚才的计算过程,对于每条边,我们可以将贡献抽象成一个函数 \(f(x) = fac\_dis[x] + bac\_dis[y] + t_i - k \times w_i\)
发现这是一个一次函数,每次查询都相当于在这些函数(直线)上取个最小值。
所以我们可以将这个东西用李超线段树维护出来,时间复杂度 \(O(n \log n)\)。
code
自认为码风很不戳(骄傲)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
namespace michaele {
typedef long long ll;
const int N = 2e5 + 10, M = 6e5 + 10, MN = N * 30, RA = 1e9;
const ll inf = 4e18;
int n, m, q;
struct Edge1 { ll fr, to, t, w; } org_e[M];
struct Edge2 { ll to, t; };
vector <Edge2> fac_e[N], bac_e[N];
ll fac_dis[N], bac_dis[N], a[M], b[M];
void dijk (int s, vector <Edge2> *e, ll *dis) {
fill (dis + 1, dis + 1 + n, inf);
priority_queue <pair <ll, int> > q;
dis[s] = 0;
q.push ({0, s});
while (q.size ()) {
int x = q.top ().second;
q.pop ();
for (auto tmp : e[x]) {
ll y = tmp.to, z = tmp.t;
if (dis[y] > dis[x] + z) {
dis[y] = dis[x] + z;
q.push ({-dis[y], y});
}
}
}
}
struct Seg {
#define f(id, x) (a[id] * x + b[id])
int t[MN], ls[MN], rs[MN], idx, rt;
void clear () { idx = 0; rt = 0; }
void change (int &p, int l, int r, int id) {
if (!p) {
p = ++ idx;
t[p] = id;
ls[p] = rs[p] = 0;
return;
}
int mid = (l + r) >> 1;
if (f (id, mid) < f (t[p], mid)) swap (id, t[p]);
if (l == r) return;
if (f (id, l) < f (t[p], l)) change (ls[p], l, mid, id);
if (f (id, r) < f (t[p], r)) change (rs[p], mid + 1, r, id);
}
ll query (int p, int l, int r, int pos) {
if (!p) return inf;
if (l == r) return f (t[p], pos);
int mid = (l + r) >> 1;
ll res = f (t[p], pos);
if (pos <= mid) res = min (res, query (ls[p], l, mid, pos));
else res = min (res, query (rs[p], mid + 1, r, pos));
return res;
}
#undef f
} tr;
void clear () {
for (int i = 1; i <= n; i ++) {
vector <Edge2> emp1, emp2;
fac_e[i].swap (emp1);
bac_e[i].swap (emp2);
}
}
void solve () {
cin >> n >> m;
for (int i = 1; i <= m; i ++) {
ll x, y, t, w;
cin >> x >> y >> t >> w;
org_e[i] = {x, y, t, w};
fac_e[x].push_back ({y, t});
bac_e[y].push_back ({x, t});
}
dijk (1, fac_e, fac_dis);
dijk (n, bac_e, bac_dis);
tr.clear ();
for (int i = 1; i <= m; i ++) {
ll x = org_e[i].fr, y = org_e[i].to;
if (fac_dis[x] == inf || bac_dis[y] == inf) continue;
a[i] = -org_e[i].w, b[i] = fac_dis[x] + bac_dis[y] + org_e[i].t;
tr.change (tr.rt, 1, RA, i);
}
cin >> q;
for (int i = 1; i <= q; i ++) {
ll k;
cin >> k;
cout << tr.query (tr.rt, 1, RA, k) << endl;
}
clear ();
}
void Main () {
int T;
cin >> T;
while (T --) {
solve ();
}
}
}
int main () {
// freopen ("test/test.in", "r", stdin);
// freopen ("test/test.out", "w", stdout);
michaele :: Main ();
return 0;
}