1. 程式人生 > 實用技巧 >「NOI2020」「LOJ #3339」「Luogu P6772」美食家

「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

  • \(1\le n\le 50\)
  • \(n\le m\le 501\)
  • \(0\le k\le 200\)
  • \(1\le t_i\le T\le 10^9\)
  • \(1\le w_i\le 5\)
  • \(1\le c_i\le 52501\)
  • \(1\le y_i\le 10^9\)

Solution

首先有一個簡單樸素的動態規劃:\(f(t, x)\) 表示在 \(t\) 時間點,走到頂點 \(x\) 時,可以得到的最大價值和。顯然有:

\[f(t, x) = \max\limits_{(y, x, w) \in \text E} \{f(t-w, y)\} + c_x + g(t, x) \]

其中 \(g(t, x)\) 表示 \(t\) 時間頂點 \(x\) 的附加值(美食節),如果沒有就是 \(0\)

這個做法是 \(O(m\times T)\) 的,期望 \(40\ \text{pts}\)


考慮優化。為了方便,\(k\) 個額外的我們會在最後討論,以下都假設 \(k=0\)

我們發現,如果 \(w_i = 1\),那麼所有邊的轉移都是“一步到位”的,不存在走了一天還在路上這種東西。那麼這樣顯然可以 矩陣乘法 優化。

具體的,就是假設矩陣 \(B_t = \begin{bmatrix}f(t, 1)\\f(t, 2)\\ \vdots \\ f(n, t) \end{bmatrix}\)

,即時間點 \(t\) 下的所有狀態。我們需要構造一個轉移矩陣 \(A\),使得 \(B_{t+1} = A\circ B_t\)。其中 \(\circ\) 為自定義的一種“廣義矩陣乘法”,計算公式即為上述狀態轉移方程:\(c_{i, j} = \max_{1\le k\le n} \{a_{i, k} + b_{k, j}\}\)。可以證明這樣定義一定滿足結合律。

不難發現轉移的條件是邊存在,於是 \(A\) 就是 反圖 (帶上價值)的鄰接矩陣。為什麼是反圖?因為這裡 \(A_{u, v}\) 的含義是 \(u\)\(v\) 轉移而來得到的價值 \(c_u\),並不是 \(u\to v\) 轉移過去。

那麼直接矩陣快速冪即可,\(B_t = A^t \circ B_0\),答案即為 \((B_t)_{1, 1}\) 的值,複雜度 \(O(n^3\log T)\)


然而邊長 \(w\) 並不是一,為了不讓我們前面的努力白費, 我們嘗試著將原圖的邊權“變成”\(1\)。本題中有一個突破口:\(w\le 5\),那麼我們的機會來了。

拆邊?我們發現點數的規模會達到 \(O(mw+n)\),不可承受。所以選擇拆點。

我們把點 \(u\) 拆成五個:\(u_1, u_2, u_3, u_4, u_5\),然後連邊 \(u_1\to u_2\to u_3\to u_4\to u_5\),邊權都為 \(0\),邊長為 \(1\),表示這些拆出來的邊不會對答案有貢獻。更新轉移矩陣 \(A_{u_w, u_{w-1}} = 0\)

如果原圖中有一條邊 \((u, v, w)\),那麼我們實際的連邊為 \(u_w \to v_1\)。不難發現,長度為 \(w\) 的邊正好被分為了 \(w\) 段長度為 \(1\) 的邊。然後更新轉移矩陣 \(A_{v, u_{w}} = c_u\)

最後得到了一個 \((nw)^2\) 大小的矩陣。

那麼現在有了一個應付 \(k=0\) 的資料的做法,加上暴力 dp 期望可得 \(35 \ \text{pts}\)


考慮如何加入附加元素(美食節)的影響。發現整個時間段 \([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 \to u_4 \to 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)^3\sum \log (t_i - t_{i-1}))\)。也就是說 我們可能會做 \(200\) 多次矩陣快速冪! 這裡會用到一個神奇優化,詳情見下:

我們預處理出 \(A\) 之後,再計算一個 \(P\)。其中 \(P_i = A^{2^i}\)。顯然 \(P_i = P_{i-1}^2\),可以在 \(O((nw)^3\log 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)^2\log(t_i - t_{i-1}))\),已經可以通過了。

總複雜度 \(O((nw)^3(\log T + \sum\log(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;
}