2022 China Collegiate Programming Contest (CCPC) Mianyang Onsite E Hammer to Fall

Luckyblock發表於2024-11-06

知識點: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\) 上的一個人不被幹掉的最小代價。則顯然有:

\[f_{i, u} = \begin{cases} f_{i + 1, u} &(b_i\not= u)\\ \min\limits_{(u, v, w)\in E} f_{i + 1, v} + w \end{cases}\]

答案即:

\[\sum_{i=1}^n a_i\times f_{1, i} \]

顯然上述方程中第一維是完全不必要的,可以直接最佳化掉。由於某些點度數是過多,直接實現上述 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;
}

相關文章