BZOJ3631松鼠的新家——树链剖分or树上差分

Description
松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的。天哪,他居然真的住在“树”上。松鼠想邀请小熊维尼前来参观,并且还指定一份参观指南,他希望维尼能够按照他的指南顺序,先去a1,再去a2,……,最后到an,去参观新家。
可是这样会导致维尼重复走很多房间,懒惰的维尼不听地推辞。可是松鼠告诉他,每走到一个房间,他就可以从房间拿一块糖果吃。维尼是个馋家伙,立马就答应了。
现在松鼠希望知道为了保证维尼有糖果吃,他需要在每一个房间各放至少多少个糖果。因为松鼠参观指南上的最后一个房间an是餐厅,餐厅里他准备了丰盛的大餐,所以当维尼在参观的最后到达餐厅时就不需要再拿糖果吃了。
Input
第一行一个整数n,表示房间个数
第二行n个整数,依次描述a1-an
接下来n-1行,每行两个整数x,y,表示标号x和y的两个房间之间有树枝相连。
Output
一共n行,第i行输出标号为i的房间至少需要放多少个糖果,才能让维尼有糖果吃。
Sample Input
5

1 4 5 3 2

1 2

2 4

2 3

4 5
Sample Output
1

2

1

2

1
HINT
2<= n <=300000


我们用树链剖分的思维来做这道题,就会发现这道题十分简单,我们每次修改树链即可。
#include<bits/stdc++.h>
#define MAXN 300005
#define MAXM 600005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,cnt,dfn,id,a[MAXN],head[MAXM],nxt[MAXM],go[MAXM],dep[MAXN],gran[MAXN][20],ans[MAXN];
int fa[MAXN],son[MAXN],siz[MAXN],tid[MAXN],top[MAXN],sum[MAXN<<2],add[MAXN<<2],pl[MAXN];
void addedge(int x,int y){
    go[cnt]=y;nxt[cnt]=head[x];head[x]=cnt;cnt++;
    go[cnt]=x;nxt[cnt]=head[y];head[y]=cnt;cnt++;
}
void dfsI(int x,int father){
    register int i;
    fa[x]=father;dep[x]=dep[father]+1;
    siz[x]=1;son[x]=-1;gran[x][0]=father;
    for(i=1;(1<<i)<=dep[x];i++) gran[x][i]=gran[gran[x][i-1]][i-1];
    for(i=head[x];i!=-1;i=nxt[i]){
        int to=go[i];
        if(to==fa[x]) continue;
        dfsI(to,x);siz[x]+=siz[to];
        if(son[x]==-1||siz[son[x]]<siz[to]) son[x]=to;
    }
}
void dfsII(int x,int t){
    register int i;
    tid[x]=++dfn;top[x]=t;pl[dfn]=x;
    if(son[x]==-1) return;
    dfsII(son[x],t);
    for(i=head[x];i!=-1;i=nxt[i]){
        int to=go[i];
        if(to==fa[x]||to==son[x]) continue;
        dfsII(to,to);
    }
}
void up(int node){sum[node]=sum[node<<1]+sum[node<<1|1];}
void down(int node,int ls,int rs){
    if(add[node]){
        add[node<<1]+=add[node];
        add[node<<1|1]+=add[node];
        sum[node<<1]+=ls*add[node];
        sum[node<<1|1]+=rs*add[node];
        add[node]=0;
    }
}
void update(int node,int l,int r,int L,int R,int ad){
    if(L<=l&&r<=R){
        add[node]+=ad;sum[node]+=(r-l+1)*ad;
        return;
    }
    int mid=(l+r)>>1;down(node,mid-l+1,r-mid);
    if(L<=mid) update(node<<1,l,mid,L,R,ad);
    if(R>mid) update(node<<1|1,mid+1,r,L,R,ad);
    up(node);
}
void query(int node,int l,int r){
    if(l==r){
        ans[pl[++id]]=sum[node];return;
    }
    int mid=(l+r)>>1;down(node,mid-l+1,r-mid);
    query(node<<1,l,mid);
    query(node<<1|1,mid+1,r);
}
int lca(int x,int y){
    register int i;
    if(dep[x]>dep[y]) swap(x,y);
    for(i=19;i>=0;i--)
      if(dep[x]<=dep[gran[y][i]]) y=gran[y][i];
    for(i=19;i>=0;i--)
      if(gran[x][i]!=gran[y][i]) x=gran[x][i],y=gran[y][i];
    if(x!=y) x=gran[x][0];
    return x;
}
void uplink(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]>dep[top[y]]) swap(x,y);
        update(1,1,n,tid[top[y]],tid[y],1);
        y=fa[top[y]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    update(1,1,n,tid[x],tid[y],1);
}
void print(int x){
    if(x/10) print(x/10);
    putchar(x%10+'0');
}
int main()
{
    n=read();register int i,j;
    memset(head,-1,sizeof(head));
    for(i=1;i<=n;i++) a[i]=read();
    for(i=1;i<n;i++){
        int x=read(),y=read();
        addedge(x,y);
    }
    dfsI(1,0);dfsII(1,1);
    for(i=1;i<n;i++){
        if(a[i]==a[i+1]) continue;
        if(lca(a[i],a[i+1])!=a[i]) uplink(fa[a[i]],a[i+1]);
        else for(j=head[a[i]];j!=-1;j=nxt[j]){
            int to=go[j];
            if(to!=fa[a[i]]&&lca(to,a[i+1])==to) uplink(to,a[i+1]);
        }
    }
    query(1,1,n);ans[a[1]]++;ans[a[n]]--;
    for(i=1;i<=n;i++) print(ans[i]),puts("");
    return 0;
}

但是今天主要讲的是另一种思路:树上差分。树上差分思路类似于差分法。虽然很多树上差分的题都可以用树剖来做(比如这道题),但是这种重要的思想还是十分值得了解的。
差分的思想指的就是将对于一段区间的修改变为对一些端点的修改,比如树状数组里进行的区间修改单点查询就需要进行差分。而树上差分就是将差分的思想用到树上。我们定义tmp数组,表示某个点的出现次数。我们要将树上x点到y点的树链上的点值全都加上1,则 tmp[x]+=1,tmp[y]+=1,tmp[lca(x,y)]=1,tmp[fa[lca(x,y)]]=1 t m p [ x ] + = 1 , t m p [ y ] + = 1 , t m p [ l c a ( x , y ) ] − = 1 , t m p [ f a [ l c a ( x , y ) ] ] − = 1 。这样讲可能比较抽象,由于我们要上推tmp,所以直接改变最两端的tmp,相对应的就是从x到根上的所有点+1,从y到根的所有点+1。这样从x到y的所有点都+1,从lca(x,y)到根的所有点都+2.然而我们只需要x到y的所有点都+1,所以从lca(x,y)到根的所有点都-1,然后fa[lca(x,y)]到根所有点都-1,这样lca(x,y)变为1,fa[lca(x,y)]到根的所有点都变为0,就符合了要求。
最后再dfs求出所有的tmp即可。
#include<bits/stdc++.h>
#define MAXN 300005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,cnt,a[MAXN],tmp[MAXN],head[MAXN<<1],nxt[MAXN<<1];
int go[MAXN<<1],gran[MAXN][20],ans[MAXN],fa[MAXN],dep[MAXN];
void addedge(int x,int y){
    go[cnt]=y;nxt[cnt]=head[x];head[x]=cnt;cnt++;
    go[cnt]=x;nxt[cnt]=head[y];head[y]=cnt;cnt++;
}
void dfsI(int x,int father){
    gran[x][0]=father;dep[x]=dep[father]+1;
    for(int i=1;(1<<i)<=dep[x];i++) gran[x][i]=gran[gran[x][i-1]][i-1];
    for(int i=head[x];i!=-1;i=nxt[i]){
        int to=go[i];
        if(to==gran[x][0]) continue;
        dfsI(to,x);
    }
}
void print(int x){
    if(x/10) print(x/10);
    putchar(x%10+'0');
}
int lca(int x,int y){
    if(dep[x]>dep[y]) swap(x,y);
    for(int i=19;i>=0;i--)
     if(dep[x]<=dep[gran[y][i]]) y=gran[y][i];
    for(int i=19;i>=0;i--)
     if(gran[x][i]!=gran[y][i]) x=gran[x][i],y=gran[y][i];
    if(x!=y) y=gran[y][0];
    return y;
}
void down(int x){
    for(int i=head[x];i!=-1;i=nxt[i]){
        int to=go[i];
        if(to==gran[x][0]) continue;
        down(to);tmp[x]+=tmp[to];
    }
}
int main()
{
    n=read();
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<n;i++){
        int x=read(),y=read();
        addedge(x,y);
    }
    dfsI(1,0);
    for(int i=1;i<n;i++){
        tmp[a[i]]++;tmp[a[i+1]]++;
        int LCA=lca(a[i],a[i+1]);
        tmp[LCA]--;tmp[gran[LCA][0]]--;
    }
    down(1);
    for(int i=2;i<=n;i++) tmp[a[i]]--;
    for(int i=1;i<=n;i++) print(tmp[i]),puts("");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值