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 草过去了,导致这题做不出来,输!
简述
给定一 个节点 条边的无向连通图 ,边有边权 。初始时第 个节点上有 个人。你可以在任意时间进行任意次操作,每次操作将某个节点上的一个人立刻转移到相邻的节点上,代价为经过的边的权值。
现在按时间顺序有 次事件发送,第 次事件将给定节点编号 ,表示在时刻 节点 上将降下重锤干掉该节点所有人。
现在要通过若干操作保证没有人被干掉,求所有操作代价之和的最小值,答案对 998244353 取模。
,,,。
3S,1024MB。
分析
显然仅会在每次重锤即将降下之前,将目标节点上的人全部转移,且一定是将这些人全部一起转移到相邻的某一个节点上。于是对于每个时刻的最优决策,仅需考虑之后所有时刻的情况即可,于是套路地考虑倒序枚举时间进行转移。
记 表示倒序枚举到时刻 ,在节点 上的一个人不被干掉的最小代价。则显然有:
答案即:
显然上述方程中第一维是完全不必要的,可以直接优化掉。由于某些点度数是过多,直接实现上述 DP 时间复杂度为 级别,显然不可接受,但是发现有 ,于是套路地考虑按照度数根号分治。
考虑设定阈值 ,对于度数不大于 的节点直接按照上述转移方程做;对于度数大于 的点,考虑预处理其邻接点的信息便于直接转移。但根据上述转移过程,预处理的邻接点中可能有某些点在预处理之后又被更新到了,从而导致直接转移出错。
于是一个套路是再对时间进行分块。考虑对度数大于 的每个节点预处理其邻接点 中 的前 小,且每经过时间 就 地大力重构所有节点预处理的信息,转移时仅需枚举预处理的上述 个邻接点进行转移即可。因为每个时间块内至多进行 次操作,显然可以保证一定枚举到了最优的转移。
重构时考虑枚举所有点的邻接点,并使用 nth_element
取前 小,总时间复杂度为 级别,单次转移时间复杂度为 级别,总时间复杂度 级别,取 时上式取最小值为 。
代码
复制复制// /* 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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】