题解 P1600 【天天爱跑步】

Solution 天天爱跑步

题目大意:给定一棵树,有\(m\)个人会从\(u\)沿着最短路径跑到\(v\),通过每条边的时间为\(1\),每个人于时刻\(0\)同时起跑.如果一个人在时刻\(w_i\)跑到点\(i\)那么点\(i\)答案\(+1\),求每个点答案

树上差分


分析:

我们直接统计观察员的答案不方便的话我们可以考虑统计每段路径对答案的贡献

然后此题关键是拆分路径

假如\(lca(u,v) = l\),我们定义往根走为上行,往叶子节点走为下行.那么每段路径都是由上行路径和下行路径组成的.我们分别统计答案

首先统计上行路径,\(dep[u]\)表示点\(u\)深度,对于路径\(u\)\(l\),如果这段路径对点\(x\)产生贡献,那么有

\(dep[u] -dep[x]=w[x]\),移项得\(dep[x] +w[x]=dep[u]\)

然后问题变成了:把每个点看做一个"桶",把一段路径上的所有点放入数\(dep[u]\),最后询问每个点\(x\)的桶内有多少个数等于\(dep[x]+w[x]\)

这玩意儿可以树上差分,但是略有不同.普通的差分是直接对数进行差分,这里的差分是对桶进行差分.(可以线段树合并)

我们在\(u\)点桶内放入\(dep[u]\),在\(faz[l]\)桶内放入\(dep[u]\)的相反数,那么我们对子树求和,得到的桶就是当前点\(x\)的桶

关键是我们不可能开那么多桶并且暴力合并,时空复杂度都无法承受,我们考虑复用,既然我们要求的是桶\(dep[x]+w[x]\)为止的值,我们直接把进栈和退栈时的值都求出来,求个增量就好

然后每个点对桶进行的操作我们可以用\(vector\)来保存,总大小与\(m\)同阶

我们再看下行路径,路径对一个点\(x\)产生贡献的条件

\(dep[u]-dep[l]+dep[x]-dep[x]=w[x]\)

\(\therefore w[x]-dep[x]=dep[u]-2 \times dep[l]\),这里下标可能出现负数,开个\(map\)或者数组平移就好

然后我们不能再和上行路径一样在\(faz[l]\)处打标记,这样\(lca\)会被统计两次,我们在\(l\)出打标记即可

下行路径:在点\(v\)放入\(dep[u]-2\times dep[l]\),在点\(l\)放入\(dep[u]-2\times dep[l]\)相反数

注意上下路径两个桶分别统计,不然会错统导致答案变大

不算\(Trick\)\(Trick\),手写个类重载\([]\)运算符,代替手动平移这样不易写挂

#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 3e5 + 100,maxdep = 25;
inline int read(){
    int x = 0;char c = getchar();
    while(!isdigit(c))c = getchar();
    while(isdigit(c))x = x * 10 + c - '0',c = getchar();
    return x;
}
struct Array{
    int val[maxn << 1];
    int operator[](const int pos)const{return val[pos + maxn];}
    int& operator[](const int pos){return val[pos + maxn];}
}cnt[2];
vector G[maxn],add[2][maxn],del[2][maxn];
inline void addedge(int from,int to){G[from].push_back(to);}
int dep[maxn],faz[maxn][maxdep + 1],w[maxn],ans[maxn],n,m;
inline void dfs_init(int u){
    dep[1] = 1;
    for(int i = 1;i <= maxdep;i++)faz[u][i] = faz[faz[u][i - 1]][i - 1];
    for(int v : G[u]){
        if(v == faz[u][0])continue;
        dep[v] = dep[u] + 1;
        faz[v][0] = u;
        dfs_init(v);
    }
}
inline int lca(int x,int y){
    if(dep[x] < dep[y])swap(x,y);
    for(int i = maxdep;i >= 0;i--)
        if(dep[faz[x][i]] >= dep[y])x = faz[x][i];
    if(x == y)return x;
    for(int i = maxdep;i >= 0;i--)
        if(faz[x][i] != faz[y][i])x = faz[x][i],y = faz[y][i];
    return faz[x][0];
}
inline void dfs_solve(int u){
    int tmp = cnt[0][dep[u] + w[u]] + cnt[1][w[u] - dep[u]];
    for(int i = 0;i < 2;i++){
        for(int x : add[i][u])cnt[i][x]++;
        for(int x : del[i][u])cnt[i][x]--;
    }
    for(int v : G[u]){
        if(v == faz[u][0])continue;
        dfs_solve(v);
    }
    ans[u] = cnt[0][dep[u] + w[u]] + cnt[1][w[u] - dep[u]] - tmp;
}
int main(){
    n = read(),m = read();
    for(int u,v,i = 1;i < n;i++)u = read(),v = read(),addedge(u,v),addedge(v,u);
    dfs_init(1);
    for(int i = 1;i <= n;i++)w[i] = read(); 
    for(int i = 1;i <= m;i++){
        int u = read(),v = read(),l = lca(u,v);
        add[0][u].push_back(dep[u]);
        del[0][faz[l][0]].push_back(dep[u]);
        add[1][v].push_back(dep[u] - 2 * dep[l]);
        del[1][l].push_back(dep[u] - 2 * dep[l]);
    }
    dfs_solve(1);
    for(int i = 1;i <= n;i++)
        printf("%d%c",ans[i],i == n ? '\n' : ' ');
    return 0;
}

你可能感兴趣的:(题解 P1600 【天天爱跑步】)