:niiid[i]1:iiid[i]ans[i]+1+(    ):退[l,r]+1:ans[l]++ans[r+1]iid[i]rootleafi[li,ri],+1[root,ri]+1[root,li1]1++ans[r]ans[l1]仿iitreeidfsPS:ri=i,li=id[i]stlinlognO(n)\begin{aligned} &题意:\hspace{0.6cm}给一棵n个点的树,每个点i对从i到i的第d[i]个父亲这一段路径上的点有1点贡献,求每个点拥有的贡献数。\\ &分析:\hspace{0.6cm}单独考虑一个点i的情况,朴素想法是从i到i的第d[i]个父亲这段路径上的ans[i]都+1,直接树剖+维护\\ &\hspace{1.2cm}区间加法的方法也可,由于个人原因(~~太菜写不动树剖而当场放弃~~)最后换了种方法。\\ &思路:\hspace{0.6cm}我们不难考虑树退化成链的情况,它就是简单的一维数组,每次实现对区间[l,r]的+1操作,求最后数组\\ &\hspace{1.2cm}的所有元素值。这对应着一种简单做法: **差分**。ans[l]++,ans[r+1]--,最后做前缀和。\\ &\hspace{1.6cm}接下来我们考虑树的情况,我们每次操作范围是从i到i的第d[i]个父亲这条路径,它是从根节点root到某个\\ &\hspace{1.2cm}叶子结点leaf_i的链上的一段区间[l_i,r_i],操作内容仍为+1。这等价于[root, r_i]+1和[root,l_i-1]-1,\\ &\hspace{1.2cm},从而转化为++ans[r]和--ans[l-1]。计算答案时,我们考虑仿照单链情况做前缀和的方法,对每个\\ &\hspace{1.2cm}点i计算以i点为根的子树tree_i上所有点的贡献和,dfs即可。\\ &PS: r_i = i, l_i = i的第d[i]个父亲,由于比较菜赛时写了个st表求l_i,最终复杂度为n*logn,赛后改了个O(n)做法 \end{aligned}

赛时代码如下:(n * logn)

#include <bits/stdc++.h>
//#define int long long
using namespace std;


const int maxn = 2e6 + 20;
int t, n, d[maxn], f[maxn][20], ans[maxn];
vector<int> e[maxn];

// st表求祖先
void dfs1(int x, int fx) {
    f[x][0] = fx;
    for(int i = 1;f[x][i-1];++i) f[x][i] = f[f[x][i-1]][i-1];
    for(int y : e[x]) {
        if(y == fx) continue;
        dfs1(y, x);
    }
}

// 求x的第k级祖先
int ask(int x,int k) {
    for(;k;k-=k&(-k)){
        x = f[x][__lg(k&-k)];
    }
    return x;
}

// 统计子树权值和
void dfs2(int x, int fx) {
    for(int y : e[x]) {
        if(y == fx) continue;
        dfs2(y, x);
        ans[x] += ans[y];
    }
}

signed main() {
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin >> n;
    // 建边
    for(int i = 1; i < n; ++i) {
        int u,v;
        cin>>u>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    for(int i = 1; i <= n; ++i) cin >> d[i];
    dfs1(1, 0); // 预处理祖先
    for(int i = 1; i <= n; ++i) {// 进行所有"区间"操作
        ++ans[i];
        --ans[ask(i, d[i]+1)];
    }
    dfs2(1, 0); // 统计子树权值和
    for(int i = 1; i <= n; ++i) cout << ans[i] << " \n"[i==n];
    return 0;
}

O(n) 写法如下:

#include <bits/stdc++.h>
//#define int long long
using namespace std;


const int maxn = 2e6 + 20;
int t, n, d[maxn], ans[maxn];
vector<int> e[maxn], path;

// 进行所有"区间"操作
void dfs1(int x, int fx) {
    path.push_back(x);
    int pos = max(0, (int)(path.size()-1) - d[x] - 1); // 注意: 长为d[x]的路径有d[x]+1个点
    --ans[path[pos]]; // l[x] = path[pos]
    ++ans[x]; // r[x] = x
    for(int y : e[x]) {
        if(y == fx) continue;
        dfs1(y, x);
    }
    path.pop_back();
}

// 统计子树权值和
void dfs2(int x, int fx) {
    for(int y : e[x]) {
        if(y == fx) continue;
        dfs2(y, x);
        ans[x] += ans[y];
    }
}

signed main() {
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin >> n;
    // 建边
    for(int i = 1; i < n; ++i) {
        int u,v;
        cin>>u>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    for(int i = 1; i <= n; ++i) cin >> d[i];
    path.push_back(0);
    dfs1(1, 0); // 进行所有"区间"操作
    dfs2(1, 0); // 统计子树权值和
    for(int i = 1; i <= n; ++i) cout << ans[i] << " \n"[i==n];
    return 0;
}