zoukankan      html  css  js  c++  java
  • 「NOI2020」「LOJ #3339」「Luogu P6772」美食家

    Description

    给定一张有向图,(n) 个顶点,(m) 条边。第 (i) 条边从 (u_i)(v_i),走完该边的用时为 (w_i)。每一个点有一个价值 (c),走到点 (i) 可以得到 (c_i) 的价值。

    初始时间为 (0),你需要从起点 (1) 开始,走出一个回到 (1) 的有向环,耗时恰好为 (T)。最终得到的价值为所有经过的点的价值和。注意这里的环可以经过同个顶点多次,价值和也会被计算多次。

    现在有 (k) 个附加元素,第 (i) 个附加元素有三个参数:((t_i, x_i, y_i))。表示当恰好在 (t_i) 时间点到达顶点 (x_i) 时,可以得到 (y_i) 的额外的价值。

    求最大的最终价值和。

    Hint

    • (1le nle 50)
    • (nle mle 501)
    • (0le kle 200)
    • (1le t_ile Tle 10^9)
    • (1le w_ile 5)
    • (1le c_ile 52501)
    • (1le y_ile 10^9)

    Solution

    首先有一个简单朴素的动态规划:(f(t, x)) 表示在 (t) 时间点,走到顶点 (x) 时,可以得到的最大价值和。显然有:

    [f(t, x) = maxlimits_{(y, x, w) in ext E} {f(t-w, y)} + c_x + g(t, x) ]

    其中 (g(t, x)) 表示 (t) 时间顶点 (x) 的附加值(美食节),如果没有就是 (0)

    这个做法是 (O(m imes T)) 的,期望 (40 ext{pts})


    考虑优化。为了方便,(k) 个额外的我们会在最后讨论,以下都假设 (k=0)

    我们发现,如果 (w_i = 1),那么所有边的转移都是“一步到位”的,不存在走了一天还在路上这种东西。那么这样显然可以 矩阵乘法 优化。

    具体的,就是假设矩阵 (B_t = egin{bmatrix}f(t, 1)\f(t, 2)\ vdots \ f(t, n) end{bmatrix}),即时间点 (t) 下的所有状态。我们需要构造一个转移矩阵 (A),使得 (B_{t+1} = Acirc B_t)。其中 (circ) 为自定义的一种“广义矩阵乘法”,计算公式即为上述状态转移方程:(c_{i, j} = max_{1le kle n} {a_{i, k} + b_{k, j}})。可以证明这样定义一定满足结合律。((A^x = Acirc Acirc cdots circ A)(x)(A)

    不难发现转移的条件是边存在,于是 (A) 就是 反图 (带上价值)的邻接矩阵。为什么是反图?因为这里 (A_{u, v}) 的含义是 (u)(v) 转移而来得到的价值 (c_u),并不是 (u o v) 转移过去。

    那么直接矩阵快速幂即可,(B_t = A^t circ B_0),答案即为 ((B_t)_{1, 1}) 的值,复杂度 (O(n^3log T))


    然而边长 (w) 并不是一,为了不让我们前面的努力白费, 我们尝试着将原图的边权“变成”(1)。本题中有一个突破口:(wle 5),那么我们的机会来了。

    拆边?我们发现点数的规模会达到 (O(mw+n)),不可承受。所以选择拆点。

    我们把点 (u) 拆成五个:(u_1, u_2, u_3, u_4, u_5),然后连边 (u_1 o u_2 o u_3 o u_4 o u_5),边权都为 (0),边长为 (1),表示这些拆出来的边不会对答案有贡献。更新转移矩阵 (A_{u_w, u_{w-1}} = 0)

    如果原图中有一条边 ((u, v, w)),那么我们实际的连边为 (u_w o v_1)。不难发现,长度为 (w) 的边正好被分为了 (w) 段长度为 (1) 的边。然后更新转移矩阵 (A_{v, u_{w}} = c_u)

    最后得到了一个 ((nw)^2) 大小的矩阵。

    那么现在有了一个应付 (k=0) 的数据的做法。


    考虑如何加入附加元素(美食节)的影响。发现整个时间段 ([0, T]) 被时间点 (t_1, t_2, cdots , t_k) 分为了一些段,那我们不如 一段段 地做。

    具体的,当 (t_{i-1}) 刚做好时,我们直接跳到 (t_i),然后在点 (x_i) 的对应值加上 (y_i)。于是问题被完美地解决了……

    显然还没有。这里有两个问题:

    • (x_i) 加到那个点上?
    • 复杂度好像有点炸……

    下面给出解决方案:

    • 由于我们进行了拆点操作,点 (x_i) 应对于 (5) 个点中的哪一个呢?假设有一条 (u_1 o u_4 o v_1) 的路径,其对应的原图的边其实是 ((u, v, 4))。对于点 (u_4) 而言,它的实际含义其实是“在路上”。对于 (u_2, u_3, u_5) 同理。可见只有点 (u_1) 才是真正原图上的顶点。于是一个把 (y_i) 加在 ((x_i)_1) 的位置上。
    • 算一下复杂度?我们最多有 (k) 次,每次 (log (t_i - t_{i-1})) 次矩阵(方阵×方阵)乘法,总复杂度为 (O((nw)^3sum log (t_i - t_{i-1})))。也就是说 我们可能会做 (200) 多次矩阵快速幂! 这里会用到一个神奇优化,详情见下:

    我们预处理出 (A) 之后,再计算一个 (P)。其中 (P_i = A^{2^i})。显然 (P_i = P_{i-1}^2),可以在 (O((nw)^3log T)) 的时间内求出。

    然后尝试着用 (P) 替代矩阵快速幂。我们考虑一下,我们需要求 (B circ A^x) 的时候,快速幂是先计算 (A^x) 的,其中原理是 (A^x = A^{e_1} circ A^{e_2} circ cdots circ A^{e_q}),其中 (e)(x) 二进制下的所有为 (1) 的位分别独立取出的值。而我们现在已经预处理好了这些 (P),如果我们直接用 (B) 每次对 (P) 做乘法,结果是一样的。

    但这两者又有什么区别呢?当然有!我们发现 (B) 是一个 列向量,这个东西乘上 (A) 一次是 平方级别 的!

    现在我们可以做到一次跳时间点 (O((nw)^2log(t_i - t_{i-1}))),已经可以通过了。

    总复杂度 (O((nw)^3log T + (nw)^2sumlog(t_i - t_{i-1})))

    Code

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : NOI2020 LOJ #3339 Luogu P6772 美食家
     */
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    
    using namespace std;
    typedef long long LL;
    
    const int N = 50 + 5;
    const int W = 5;
    const int logT = 31;
    const int K = 200 + 5;
    const LL inf = 5e16;
    
    int n, m, T, k;
    int c[N];
    struct activity {
        int t, x, y;
    } act[K << 1];
    
    struct matrix {
        LL e[N * W][N * W];
        int R, C;
        inline LL* operator [] (int p) { return e[p]; }
        inline void set(LL v) {
            for (int i = 1; i <= R; i++)
                for (int j = 1; j <= C; j++)
                    e[i][j] = v;
        }
    } A, B, P[logT];
    // P[k] : A^{2^k}
    
    inline matrix operator * (matrix a, matrix b) {
        matrix c; c.R = a.R, c.C = b.C;
        c.set(-inf);
        for (int k = 1; k <= a.C; k++)
            for (int i = 1; i <= a.R; i++)
                for (int j = 1; j <= b.C; j++)
                    c[i][j] = max(c[i][j], a[i][k] + b[k][j]);
        return c;
    }
    
    void trans(matrix& B, int t) {
        if (!t) return;
        for (int i = 0; i < logT; i++) 
            if ((t >> i) & 1)
                B = P[i] * B;
    }
    
    signed main() {
        freopen("delicacy3.in", "r", stdin);
      //  freopen("delicacy.out", "w", stdout);
    
        ios::sync_with_stdio(false);
        cin >> n >> m >> T >> k;
    
        A.R = A.C = n * 5;
        A.set(-inf);
    
        for (int i = 1; i <= n; i++)
            for (int w = 1; w < W; w++)
                A[i + n * w][i + n * (w - 1)] = 0;
        for (int i = 1; i <= n; i++)
            cin >> c[i];
        for (int i = 1; i <= m; i++) {
            int u, v, w; cin >> u >> v >> w;
            A[v][u + n * (w - 1)] = c[u];
        }
        for (int i = 1; i <= k; i++)
            cin >> act[i].t >> act[i].x >> act[i].y;
    
        P[0] = A;
        for (int i = 1; i < logT; i++)
            P[i] = P[i - 1] * P[i - 1];
        B.R = n * 5, B.C = 1;
        B.set(-inf);
        B[1][1] = 0;
    
        sort(act + 1, act + 1 + k, [](activity& a, activity& b) {
            return a.t < b.t;
        });
        int curT = 0;
        for (int i = 1; i <= k; i++) {
            int gap = act[i].t - curT;
            trans(B, gap);
            B[act[i].x][1] += act[i].y;
            curT = act[i].t;
        }
        trans(B, T - curT);
    
        LL ans = B[1][1] + c[1];
        cout << (ans < 0 ? -1 : ans) <<endl;
        return 0;
    }
    
  • 相关阅读:
    MyBatis
    JavaAgent
    Intellij IDEA
    SVN安装总结
    git(笔记)
    springboot面试题
    spring总结
    springmvc总结
    jdbc链接数据库
    redis面试题
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/13525831.html
Copyright © 2011-2022 走看看