题解 P3258 【[JLOI2014]松鼠的新家】

· · 题解

主要讲一讲

树上差分

顾名思义,树上差分就是在树上的差分(???)

学过树状数组(区间修改)的同学应该了解,差分具有很优秀的性质

已知原数组 a[i],设差分数组 b[i]=a[i]-a[i-1]

那么就有 a[i]=b[1]+b[2]+...+b[n]

修改也很方便,例如对于区间(p,q)同时加上x,相当于 b[p]+=n,b[q+1]-=n

接下来就是把线性的差分移到树上,

同理,已知原数组a[i]表示每个点的点权,

我们设差分数组b[i]=a[i]-sum(a[j])(j为i的每个直接相连的儿子);

不难发现,叶子节点的b[i]恰好为该点点权a[i],此外,对于任意一个节点K,

a[K]=以K为根节点的子树中所有b[i]之和。

如果我们要对树上的一条链(u,v)的点权进行修改(同时加上x),只需要:

b[u]+=x;

b[v]+=x;

b[lca(u,v)]-=x;

b[fa[lca(u,v)]]-=x;

注意为了不影响到链外的其他点权,lca(u,v)的父节点需要修改,可以自己推一下。

这是点权的情况,如果是边权,只需要把一个点到父亲的边权赋给自己当作点权,

然后当作点权的情况做就好了,细节需要处理一下。

在本题中,原数组初始都是0,所以我们只需要读入、处理,最后遍历整颗树,递

归求出a[i]就ok了

代码:

#include <bits/stdc++.h>
using namespace std;
int h[300000+5],fa[300000+5][25];
int vis[300000+5],lg[300000+5],dep[300000+5];
int a[300000+5],b[300000+5];//a为糖果数,b为差分数组
int m,n,i,j,x,y,tmp;
struct EDGE{
    int from,to,next;
};
EDGE e[600000+5];
void add(int a,int b){
    tmp++;
    e[tmp].from=a;
    e[tmp].to=b;
    e[tmp].next=h[a];
    h[a]=tmp;
}
void dfs1(int k){
    int s=h[k];
    while(s!=0){
        int t=e[s].to;
        if(t!=fa[k][0]){
            fa[t][0]=k;
            dep[t]=dep[k]+1;
            dfs1(t);
        } 
        s=e[s].next;
    }
}
int lca(int a,int b){
    if(dep[a]<dep[b]){
        int t=a;
        a=b;
        b=t;
    }
    while(dep[a]!=dep[b]){
        int k=lg[dep[a]-dep[b]]-1;
        a=fa[a][k];
    }
    if(a==b) return a;
    else{
        for(j=lg[dep[a]]-1;j>=0;j--){
            if(fa[a][j]!=fa[b][j]){
                a=fa[a][j];
                b=fa[b][j];
            }
        }
    }
    return fa[a][0];
}
void dfs2(int k){
    int s=h[k];
    while(s!=0){
        int t=e[s].to;
        if(t!=fa[k][0]){
            dfs2(t);
            b[k]+=b[t];
        }
        s=e[s].next;
    }
}
int main(){
    cin>>n;
    for(i=1;i<=n;i++)
        scanf("%d",&vis[i]); 
    for(i=1;i<=n-1;i++){
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs1(1);
    for(j=1;j<=20;j++)
        for(i=1;i<=n;i++)
            fa[i][j]=fa[fa[i][j-1]][j-1];
    for(i=1;i<=n;i++)
        lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    a[vis[1]]++;
    for(i=2;i<=n;i++){
        int now=lca(vis[i],vis[i-1]);
        b[vis[i]]++;
        b[vis[i-1]]++;
        b[now]--;
        b[fa[now][0]]--;
        a[vis[i-1]]--;//链首糖果数直接减1
    }
    a[vis[n]]--;
    dfs2(1);
    for(i=1;i<=n;i++)
        cout<<a[i]+b[i]<<endl;
    return 0;
}

明天生日写篇题解开心一下