「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\)
求最大的最終價值和。
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}\)
不難發現轉移的條件是邊存在,於是 \(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;
}