知識點:DP,根號分治
Link:https://codeforces.com/gym/104065/problem/E
一個轉移受時間影響的、對操作序列按時間分塊的、根號分治的經典套路。
vp 的時候 dztlb 大神把關鍵的 DP 轉化開出來了但是吃了四十發過不了發現實現假了呃呃。之前 vp 22 西安的時候也遇到了思想完全一致的根號分治題,然而那題直接大力上 LCT 草過去了,導致這題做不出來,輸!
簡述
給定一 \(n\) 個節點 \(m\) 條邊的無向連通圖 \(G\),邊有邊權 \(w\)。初始時第 \(i\) 個節點上有 \(a_i\) 個人。你可以在任意時間進行任意次操作,每次操作將某個節點上的一個人立刻轉移到相鄰的節點上,代價為經過的邊的權值。
現在按時間順序有 \(q\) 次事件傳送,第 \(i\) 次事件將給定節點編號 \(b_i\),表示在時刻 \(i\) 節點 \(b_i\) 上將降下重錘幹掉該節點所有人。
現在要透過若干操作保證沒有人被幹掉,求所有操作代價之和的最小值,答案對 998244353 取模。
\(2\le n\le 10^5\),\(1\le m,q\le 10^5\),\(1\le a_i, w\le 10^9\),\(1\le b_i\le n\)。
3S,1024MB。
分析
顯然僅會在每次重錘即將降下之前,將目標節點上的人全部轉移,且一定是將這些人全部一起轉移到相鄰的某一個節點上。於是對於每個時刻的最優決策,僅需考慮之後所有時刻的情況即可,於是套路地考慮倒序列舉時間進行轉移。
記 \(f_{i, u}\) 表示倒序列舉到時刻 \(i\),在節點 \(u\) 上的一個人不被幹掉的最小代價。則顯然有:
答案即:
顯然上述方程中第一維是完全不必要的,可以直接最佳化掉。由於某些點度數是過多,直接實現上述 DP 時間複雜度為 \(O(n^2)\) 級別,顯然不可接受,但是發現有 \(m\le 10^5\),於是套路地考慮按照度數根號分治。
考慮設定閾值 \(B\),對於度數不大於 \(B\) 的節點直接按照上述轉移方程做;對於度數大於 \(B\) 的點,考慮預處理其鄰接點的資訊便於直接轉移。但根據上述轉移過程,預處理的鄰接點中可能有某些點在預處理之後又被更新到了,從而導致直接轉移出錯。
於是一個套路是再對時間進行分塊。考慮對度數大於 \(B\) 的每個節點預處理其鄰接點 \(v\) 中 \(f_{v}\) 的前 \(B+1\) 小,且每經過時間 \(B\) 就 \(O(m)\) 地大力重構所有節點預處理的資訊,轉移時僅需列舉預處理的上述 \(B+1\) 個鄰接點進行轉移即可。因為每個時間塊內至多進行 \(B\) 次操作,顯然可以保證一定列舉到了最優的轉移。
重構時考慮列舉所有點的鄰接點,並使用 nth_element
取前 \(B+1\) 小,總時間複雜度為 \(O\left(m\frac{q}{B}\right)\) 級別,單次轉移時間複雜度為 \(O(B)\) 級別,總時間複雜度 \(O\left(n + m + qB + m\frac{q}{B}\right)\) 級別,取 \(B = \sqrt m\) 時上式取最小值為 \(O\left(n + m + q\sqrt m\right)\)。
程式碼
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
const LL kInf = 9e18 + 2077;
const LL p = 998244353;
//=============================================================
int n, m, q, b[kN];
LL a[kN];
struct Edge {
int v;
LL w;
};
std::vector<Edge> edge[kN];
struct Node {
LL f, w;
int v;
bool operator < (const Node sec_) const {
if (f != sec_.f) return f < sec_.f;
return w < sec_.w;
}
};
LL f[kN];
std::vector<Node> s[kN];
int nowtime, lim;
//=============================================================
void rebuild() {
for (int i = 1; i <= n; ++ i) {
if ((int) edge[i].size() <= lim) continue;
s[i].clear();
for (auto [v, w]: edge[i]) s[i].push_back((Node) {f[v] + w, w, v});
std::nth_element(s[i].begin(), s[i].begin() + lim + 1, s[i].end());
}
}
void update(int u_) {
f[u_] = kInf;
if ((int) edge[u_].size() <= lim) {
for (auto [v, w]: edge[u_]) f[u_] = std::min(f[u_], f[v] + w);
return ;
}
for (int i = 0; i <= lim; ++ i) {
auto [ff, w, v] = s[u_][i];
f[u_] = std::min(f[u_], f[v] + w);
}
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n >> m >> q;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
for (int i = 1; i <= m; ++ i) {
int u, v, w; std::cin >> u >> v >> w;
edge[u].push_back((Edge) {v, w});
edge[v].push_back((Edge) {u, w});
}
for (int i = 1; i <= q; ++ i) std::cin >> b[i];
lim = lim = sqrt(2 * m) + 1;
rebuild();
for (int i = q; i; -- i) {
if (++ nowtime == lim) rebuild(), nowtime = 0;
update(b[i]);
}
LL ans = 0;
for (int i = 1; i <= n; ++ i) (ans += f[i] * a[i] % p) %= p;
std::cout << (LL) (ans % p) << "\n";
return 0;
}