题目描述
给定一个 个点的树,树有点权。
如果 为
和
的最近公共祖先(LCA),并且
和
的树上距离等于
,那么点
的答案就会加上
。
求所有点的答案。
正解
考虑暴力 dp,设 表示
的子树内离
的距离为
的点的个数,设
表示
的子树内离
的距离为
的点权和。
每次合并一颗子树,更新答案并且更新 dp 数组,时间复杂度 。
发现 dp 复杂度只跟深度有关,那么长链剖分后可以优化复杂度至 。
其实还有 dsu 的做法,每次暴力合并复杂度也没有问题,是 的。
这里给出长链剖分的代码 :
#include <bits/stdc++.h>
#define N 100005
using namespace std;
int n, k;
int o[N], d[N], a[N], totVec;
int fa[N], wson[N], dist[N];
int head[N], nex[N << 1], to[N << 1], ecnt;
long long ans[N];
vector<long long> f[N], g[N];
inline int read() {
int x = 0; char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
return x;
}
inline void addE(int u, int v) {
to[++ecnt] = v;
nex[ecnt] = head[u], head[u] = ecnt;
}
void preDfs(int u) {
for(int i = head[u], v; i; i = nex[i]) {
v = to[i];
if(v == fa[u]) continue;
fa[v] = u, preDfs(v);
if(dist[wson[u]] < dist[v])
wson[u] = v;
}
dist[u] = dist[wson[u]] + 1;
}
void dfs(int u, int top) {
if(u == top) {
o[u] = ++totVec;
f[o[u]].resize(dist[u]);
g[o[u]].resize(dist[u]);
}
if(wson[u]) {
o[wson[u]] = o[u];
d[wson[u]] = d[u] + 1;
dfs(wson[u], top);
}
++f[o[u]][d[u]];
g[o[u]][d[u]] += a[u];
for(int i = head[u], v; i; i = nex[i]) {
v = to[i];
if(v == fa[u] || v == wson[u]) continue;
dfs(v, v);
for(int j = 0; j < dist[v]; ++j)
if(k - j - 1 > 0 && k - j - 1 + d[u] < dist[top]) {
ans[u] += f[o[u]][k - j - 1 + d[u]] * g[o[v]][j];
ans[u] += g[o[u]][k - j - 1 + d[u]] * f[o[v]][j];
}
for(int j = 0; j < dist[v]; ++j) {
f[o[u]][j + 1 + d[u]] += f[o[v]][j];
g[o[u]][j + 1 + d[u]] += g[o[v]][j];
}
}
}
int main() {
n = read(), k = read();
for(int i = 1; i <= n; ++i)
a[i] = read();
for(int i = 1, u, v; i < n; ++i) {
u = read(), v = read();
addE(u, v), addE(v, u);
}
preDfs(1);
dfs(1, 1);
for(int i = 1; i <= n; ++i)
printf("%lld%c", ans[i], " \n"[i == n]);
return 0;
} 
京公网安备 11010502036488号