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

知识点: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;
}
posted @ 2024-11-06 08:18  Luckyblock  阅读(31)  评论(0编辑  收藏  举报