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;
}
posted @ 2025-10-16 15:03  michaele  阅读(6)  评论(0)    收藏  举报