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 个节点上有 ai 个人。你可以在任意时间进行任意次操作,每次操作将某个节点上的一个人立刻转移到相邻的节点上,代价为经过的边的权值。
现在按时间顺序有 q 次事件发送,第 i 次事件将给定节点编号 bi,表示在时刻 i 节点 bi 上将降下重锤干掉该节点所有人。
现在要通过若干操作保证没有人被干掉,求所有操作代价之和的最小值,答案对 998244353 取模。
2n1051m,q1051ai,w1091bin
3S,1024MB。

分析

显然仅会在每次重锤即将降下之前,将目标节点上的人全部转移,且一定是将这些人全部一起转移到相邻的某一个节点上。于是对于每个时刻的最优决策,仅需考虑之后所有时刻的情况即可,于是套路地考虑倒序枚举时间进行转移。

fi,u 表示倒序枚举到时刻 i,在节点 u 上的一个人不被干掉的最小代价。则显然有:

fi,u={fi+1,u(biu)min(u,v,w)Efi+1,v+w

答案即:

i=1nai×f1,i

显然上述方程中第一维是完全不必要的,可以直接优化掉。由于某些点度数是过多,直接实现上述 DP 时间复杂度为 O(n2) 级别,显然不可接受,但是发现有 m105,于是套路地考虑按照度数根号分治。

考虑设定阈值 B,对于度数不大于 B 的节点直接按照上述转移方程做;对于度数大于 B 的点,考虑预处理其邻接点的信息便于直接转移。但根据上述转移过程,预处理的邻接点中可能有某些点在预处理之后又被更新到了,从而导致直接转移出错。

于是一个套路是再对时间进行分块。考虑对度数大于 B 的每个节点预处理其邻接点 vfv 的前 B+1 小,且每经过时间 BO(m) 地大力重构所有节点预处理的信息,转移时仅需枚举预处理的上述 B+1 个邻接点进行转移即可。因为每个时间块内至多进行 B 次操作,显然可以保证一定枚举到了最优的转移。

重构时考虑枚举所有点的邻接点,并使用 nth_element 取前 B+1 小,总时间复杂度为 O(mqB) 级别,单次转移时间复杂度为 O(B) 级别,总时间复杂度 O(n+m+qB+mqB) 级别,取 B=m 时上式取最小值为 O(n+m+qm)

代码

复制复制
//
/*
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 @   Luckyblock  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示