P2664 树上游戏
知识点:点分治
原题面 Luogu。
简述
给定一棵 个节点的树,节点 的颜色为 。定义 表示节点 到节点 简单路径上的颜色种类数,定义 ,求:。
。
1S,128MB。
分析
套路地考虑点分治,先钦定一个根节点 ,仅考虑处理过根节点的路径,并递归处理不过根节点的路径。
考虑先处理出以根为端点的链的颜色种类数,并更新 。从路径的角度统计贡献并不容易,考虑从颜色的角度统计。从 开始 dfs,并维护一个 数组储存当前节点到根路径上各颜色出现的次数。若 dfs 到节点 时有 ,说明以 的子树节点为一端点,根为一端点的路径上都会出现颜色 , 应增加 。
再考虑对非根节点的贡献。考虑在上一步中预处理一些值:所有链的贡献量 (即 的增量) ,各颜色有贡献的路径数量 。若在上一步中 dfs 到节点 时有 ,则令 ,。
之后考虑 dfs 处理非根节点的答案。对于 dfs 到的节点 ,设 所在的 的儿子为 ,过根节点且过节点 的路径显然有 条。将这样的路径 拆成链 与链 分别考虑它们对 的贡献。
对于链 ,它在每一条有贡献的路径中都被包含。考虑在 dfs 枚举 时通过维护当前节点到根路径上各颜色出现的次数,处理出其中的颜色种类数,乘上路径数量 即为其贡献。
对于所有链 ,先考虑从所有链的贡献 与各颜色的贡献 中减去子树 的贡献。具体地,考虑枚举子树 的节点 。若某颜色在 的路径上是第一次出现,则令 ,。处理完成后 与 仅包含其他子树的信息。之后进行 dfs 枚举节点 并更新 ,若某颜色在路径 上出现,说明该颜色的贡献已经在上一步统计 时已经统计过,的则令 。此时所有链 对 的贡献即为 。统计两种链的贡献可以在一个 dfs 中完成。
注意统计完成后需要还原子树 的贡献。
上述过程均可在 的时间复杂度内完成,总时间复杂度是常数巨大的 。
代码
复制复制//知识点:点分治 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #define LL long long const int kN = 1e5 + 10; //============================================================= int n, m, e_num, col[kN], head[kN], v[kN << 1], ne[kN << 1]; int root, sumsz, sz[kN], maxsz[kN]; LL deltasz, num, sum, cnt[kN], colsz[kN], ans[kN]; bool vis[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Chkmax(int &fir, int sec) { if (sec > fir) fir = sec; } void Chkmin(int &fir, int sec) { if (sec < fir) fir = sec; } void Add(int u_, int v_) { v[++ e_num] = v_, ne[e_num] = head[u_], head[u_] = e_num; } void CalcSize(int u_, int fa_) { sz[u_] = 1, maxsz[u_] = 0; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (v_ == fa_ || vis[v_]) continue; CalcSize(v_, u_); Chkmax(maxsz[u_], sz[v_]); sz[u_] += sz[v_]; } Chkmax(maxsz[u_], sumsz - sz[u_]); if (maxsz[u_] < maxsz[root]) root = u_; } void Dfs1(int u_, int fa_) { //求得所有链的贡献 sz[u_] = 1; cnt[col[u_]] ++; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (v_ == fa_ || vis[v_]) continue; Dfs1(v_, u_); sz[u_] += sz[v_]; } if (cnt[col[u_]] == 1) { sum += sz[u_]; colsz[col[u_]] += sz[u_]; } -- cnt[col[u_]]; } void Modify(int u_, int fa_, int val_) { //更改子树的贡献 cnt[col[u_]] ++; if (cnt[col[u_]] == 1) { // sum += val_ * sz[u_]; colsz[col[u_]] += val_ * sz[u_]; } for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (v_ == fa_ || vis[v_]) continue; Modify(v_, u_, val_); } cnt[col[u_]] --; } void Dfs2(int u_, int fa_) { //统计链 root -> u 与 链 root -> v 的贡献 cnt[col[u_]] ++; if (cnt[col[u_]] == 1) { //颜色 col[u] 在链 root -> u 中出现过,需要去除该颜色在其他链 root -> v 中的贡献 sum -= colsz[col[u_]]; ++ num; //num 为链 root -> u 上的颜色种类数 } ans[u_] += sum + num * deltasz; //更新 for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (v_ == fa_ || vis[v_]) continue; Dfs2(v_, u_); } if (cnt[col[u_]] == 1) { //回溯时还原 sum += colsz[col[u_]]; -- num; } -- cnt[col[u_]]; } void Clear(int u_, int fa_) { cnt[col[u_]] = colsz[col[u_]] = 0; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (v_ == fa_ || vis[v_]) continue; Clear(v_, u_); } } void Dfs(int u_, int fa_) { vis[u_] = true; Dfs1(u_, fa_); ans[u_] += sum; //对 root 的贡献 for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (v_ == fa_ || vis[v_]) continue; cnt[col[u_]] ++; //清除子树 v 的贡献。根 u 为必经点,需要初始化其数量 sum -= sz[v_], colsz[col[u_]] -= sz[v_]; Modify(v_, u_, -1); cnt[col[u_]] --; deltasz = sz[u_] - sz[v_]; //路径总数 Dfs2(v_, u_); cnt[col[u_]] ++; //还原子树 v 的贡献 sum += sz[v_], colsz[col[u_]] += sz[v_]; Modify(v_, u_, 1); cnt[col[u_]] --; } sum = num = 0; //注意清空 Clear(u_, fa_); for (int i = head[u_]; i; i = ne[i]) { //分治 int v_ = v[i]; if (v_ == fa_ || vis[v_]) continue; sumsz = sz[v_]; root = 0, maxsz[root] = kN; CalcSize(v_, u_), CalcSize(root, 0), Dfs(root, 0); } } //============================================================= int main() { n = read(); for (int i = 1; i <= n; ++ i) col[i] = read(); for (int i = 1; i < n; ++ i) { int u_ = read(), v_ = read(); Add(u_, v_), Add(v_, u_); } sumsz = n; root = 0, maxsz[root] = kN; CalcSize(1, 0), CalcSize(root, 0), Dfs(root, 0); for (int i = 1; i <= n; ++ i) printf("%lld\n", ans[i]); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】