【jzoj4904】【NOIP2016提高组】【天天爱跑步】【数据结构】

题目大意

这里写图片描述

解题思路

把无根树转换成有根树,把一条路径拆成两条路径。考虑上行的部分,能贡献的点时间差等于距离差,通过移项只与点有关,答案为子树中和根相同的的数的个数,可以在起点打一个+1标记,在lca打上一个-1标记,贡献数为子树和,下行的情况类似处理。用dfs序标记后可以转换成区间和,可以把区间和转换成前缀和,把询问拆成两个,进入点时减去之前的贡献,离开前加入贡献。因为是单点修改单点查询,所以可以用桶实现。

code

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
#define max(x,y) ((x>y)?x:y)
#define min(x,y) ((x<y)?x:y)
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define fd(i,j,k) for(int i=j;i>=k;i--)
using namespace std;
int const maxn=3*1e5;
int n,m,gra[5],w[maxn+10],cnt[3][maxn*3+10],begin[5][maxn+10],
to[5][maxn*2+10],next[5][maxn*2+10],fa[maxn+10][21],dep[maxn+10],
ans[maxn+10],two[21];
void insert(int pos,int u,int v){
    to[pos][++gra[pos]]=v;
    next[pos][gra[pos]]=begin[pos][u];
    begin[pos][u]=gra[pos];
}
void dfs(int now,int pre){
    for(int i=begin[2][now];i;i=next[2][i])
        if(to[2][i]!=pre){
            dep[to[2][i]]=dep[now]+1;
            fa[to[2][i]][0]=now;
            dfs(to[2][i],now);
        }
}
void dfss(int now,int pre){
    ans[now]-=cnt[0][w[now]+dep[now]+maxn]+cnt[1][w[now]-dep[now]+maxn];
    fo(j,0,1)
        for(int i=begin[j][now];i;i=next[j][i])
            cnt[j][to[j][i]+maxn]++;
    for(int i=begin[2][now];i;i=next[2][i])
        if(to[2][i]!=pre)dfss(to[2][i],now);
    for(int i=begin[3][now];i;i=next[3][i])
        cnt[3-3][to[3][i]+maxn]--;
    ans[now]+=cnt[0][w[now]+dep[now]+maxn]+cnt[1][w[now]-dep[now]+maxn];
    for(int i=begin[4][now];i;i=next[4][i])
        cnt[4-3][to[4][i]+maxn]--;
}
int lc(int u,int v,int &len){
    if(dep[u]<dep[v])swap(u,v);
    fd(j,19,0)
        if(dep[fa[u][j]]>=dep[v]){
            u=fa[u][j];
            len+=two[j];
        }
    if(u==v)return u;
    fd(j,19,0)
        if(fa[u][j]!=fa[v][j]){
            u=fa[u][j];
            v=fa[v][j];
            len+=two[j]*2;
        }
    len+=2;
    return fa[u][0];
}
int main(){
    freopen("running.in","r",stdin);
    freopen("running.out","w",stdout);
    scanf("%d%d",&n,&m);
    two[0]=1;fo(i,1,20)two[i]=two[i-1]*2;
    int u,v;
    fo(i,1,n-1){
        scanf("%d%d",&u,&v);
        insert(2,u,v);insert(2,v,u);
    }
    dep[1]=1;dfs(1,0);
    fo(j,1,19)
        fo(i,1,n)
            fa[i][j]=fa[fa[i][j-1]][j-1];
    fo(i,1,n)scanf("%d",&w[i]);
    int s,t,lca,len;
    fo(i,1,m){
        scanf("%d%d",&s,&t);
        len=0,lca=lc(s,t,len);
        insert(0,s,dep[s]),insert(3,lca,dep[s]);
        insert(1,t,len-dep[t]),insert(4,lca,len-dep[t]);
    }
    dfss(1,0);
    fo(i,1,n)printf("%d ",ans[i]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值