ABC163F
正难则反,考虑用所有路径减去不包含颜色 k 的路径。
删掉所有颜色为 k 的节点,发现树分成了好多个连通块,设它们的大小为 s1,s2,⋯,那么不包含颜色 k 的路径条数就是
∑si×(si+1)2
暴力做是 O(n2) 的。
设 f(x)=x×(x+1)2。
设 ans(i) 为颜色为 i 的答案,初始时均为 f(n)。
设 sum(i) 表示当前遍历过的点,颜色为 i 的节点的子树总大小,注意如果有包含就取最大的,siz(i) 表示以 i 为根的子树大小。
若当前在点 u,先保存下此前的 sum(cu),记为 tmp,接下来要遍历它的某个儿子 v,那么先清空 sum(cu),然后进入 v 的子树,遍历完后 ans(cu)←ans(cu)−f(siz(v)−sum(cu)),siz(u)←siz(u)+siz(v)。
当它的所有儿子遍历完后,更新 sum(cu)←tmp+siz(u)。
注意不能漏下最上面的连通块,最后还要 ans(i)←ans(i)−f(n−sum(i))。
时间复杂度 O(n)。
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 200005;
int n;
int head[N], ver[N*2], nxt[N*2], cnt;
int c[N];
int sum[N], siz[N];
ll ans[N];
ll calc(int x) { return 1ll * x * (x + 1) / 2; }
void add(int u, int v) {
ver[++cnt] = v, nxt[cnt] = head[u], head[u] = cnt;
}
void dfs(int u, int fa) {
siz[u] = 1;
int tmp = sum[c[u]];
for (int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if (v == fa) continue;
sum[c[u]] = 0, dfs(v, u), siz[u] += siz[v];
ans[c[u]] -= calc(siz[v] - sum[c[u]]);
}
sum[c[u]] = tmp + siz[u];
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &c[i]), ans[i] = calc(n);
for (int i = 1, u, v; i < n; ++i) scanf("%d%d", &u, &v), add(u, v), add(v, u);
dfs(1, 0);
for (int i = 1; i <= n; ++i) ans[i] -= calc(n - sum[i]);
for (int i = 1; i <= n; ++i) printf("%lld\n", ans[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通