cf739 B. Alyona and a tree(树上差分,倍增/pbds)
题意:
一棵带点权 \(val(u)\) 和边权的树,1号点为根节点。若 \(v\) 在 \(u\) 的子树中,\(v\neq u\) 且 \(dis(u,v)\le val(v)\) ,则称 \(v\) 被 \(u\) 控制。问每个点能控制多少个点。
思路:
\(u\) 能控制 \(v\) 即 \(dis(u,v)\le val(v) \iff dis(v)-dis(u)\le val(v) \iff dis(u)>= dis(v)-val(v)\)
法一:pbds,rb_tree,又叫 ordered_set
对每个 \(u\) ,只需找出 “集合 S 中小于等于 \(dis(u)\) 的值的数量”,其中 S 为 u 的子树中的所有点(不包括u自己)的 \(dis(v)-val(v)\) 值的集合。
用 order_of_key() 求严格小于 \(dis(u)+1\) 的点数。为确保只在 u 的子树中找,记录搜u的子树之前和之后的值然后作差。
注意 ordered_set 中的元素不能重复,所以存成 pair 防止重复。查找的时候取second为0保证不会算入刚好等于 \(dis(u)+1\) 的点,同时算入全部小于等于 \(dis(u)\) 的点
实测速度和内存都很优秀,真香!
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;
tree<pair<ll,int>,null_type,less<pair<ll,int>>,rb_tree_tag,
tree_order_statistics_node_update> S;
int t;
ll dis[N]; int ans[N];
void dfs(int u)
{
int before = S.order_of_key({dis[u] + 1, 0});
for(int i = h[u]; i; i = ne[i])
{
int v = e[i];
dis[v] = dis[u] + w[i];
dfs(v);
}
int after = S.order_of_key({dis[u] + 1, 0});
ans[u] = after - before;
S.insert({dis[u] - val[u], t++});
}
dfs(1);
法二:树上倍增+差分
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 5, M = N, t = 18;
int h[N], e[M], ne[M], idx;
int n, w[M], val[N]; //点权和边权
void add(int a, int b, int c) {
e[++idx] = b, ne[idx] = h[a], h[a] = idx, w[idx] = c;
}
ll dis[N]; int ans[N], f[N][t+5];
void dfs(int u)
{
for(int i = 1; i <= t; i++) f[u][i] = f[f[u][i-1]][i-1];
int fa = u; //倍增找最远的控制u的点
for(int i = t; i >= 0; i--)
if(f[fa][i] && dis[f[fa][i]] >= dis[u]-val[u]) fa = f[fa][i];
ans[f[u][0]]++, ans[f[fa][0]]--; //差分
for(int i = h[u]; i; i = ne[i])
{
int v = e[i];
dis[v] = dis[u] + w[i];
dfs(v);
ans[u] += ans[v]; //前缀和
}
}
signed main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &val[i]);
for(int i = 2, w; i <= n; i++) //直接记录每个点的父节点
scanf("%d%d", &f[i][0], &w), add(f[i][0], i, w);//p->i
dfs(1);
for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
return 0;
}
另外还有树上二分+差分、树上并查集之类的解法,不搞了

浙公网安备 33010602011771号