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;
}

另外还有树上二分+差分、树上并查集之类的解法,不搞了

posted @ 2021-12-26 12:14  Bellala  阅读(258)  评论(0)    收藏  举报