bzoj4719: [Noip2016]天天爱跑步 树上差分

Posted nan-cheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj4719: [Noip2016]天天爱跑步 树上差分相关的知识,希望对你有一定的参考价值。

Description

小c同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。?天天爱跑步?是一个养成类游戏,需要
玩家每天按时上线,完成打卡任务。这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两
个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到N的连续正整数。现在有个玩家,第个玩家的
起点为Si ,终点为Ti 。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度,
不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以
每个人的路径是唯一的)小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选
择在第Wj秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点J 。 小C想知道
每个观察员会观察到多少人?注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时
间后再被观察员观察到。 即对于把结点J作为终点的玩家: 若他在第Wj秒重到达终点,则在结点J的观察员不能观察
到该玩家;若他正好在第Wj秒到达终点,则在结点的观察员可以观察到这个玩家。

Input

第一行有两个整数N和M 。其中N代表树的结点数量, 同时也是观察员的数量, M代表玩家的数量。
接下来n-1 行每行两个整数U和V ,表示结点U 到结点V 有一条边。
接下来一行N 个整数,其中第个整数为Wj , 表示结点出现观察员的时间。
接下来 M行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证 。
1<=Si,Ti<=N,0<=Wj<=N

Output

输出1行N 个整数,第个整数表示结点的观察员可以观察到多少人。

技术分享图片

首先可以得到两个式子

(dep[u]-dep[x]=w[x])

(dep[u]-2*dep[lca]+dep[x]=w[x])

换位得到

(w[x]+dep[x]=dep[u])

(w[x]-dep[x]=dep[u]-2*dep[lca])

(x)的值确定的时候,左边的值是确定的,因此我们考虑用x的子树更新(x)的答案。
用两个桶记录值,每(dfs)到一个点更新桶的值的个数。
(dep[u])(dep[u]-2*dep[lca])更新桶的情况,
(w[x]+dep[x])(w[x]-dep[x])查询(dfs)(x)点时的答案。

注意(w[x]-dep[x])的答案可能为负,所以要加上(maxn)

回溯时更新当前节点的答案,答案更新时减去父亲节点的答案

对于(lca)等于起点和终点的特殊考虑。
另外,对于分裂成两条链(lca)可能会被统计两遍,最后特殊判断一下,如果被统计了两遍就减去一遍,

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector> 
const int maxn=3e5+10;
const int k=448;
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Dec(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch==‘-‘)f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-‘0‘;ch=getchar();}
    return x*f;
}
int n,m;
int fa[maxn],w[maxn],s[maxn],t[maxn];
vector<int>mp[maxn];
int son[maxn],size[maxn],top[maxn],dep[maxn];
struct que{int v,tag;};
vector<que>w1[maxn];
vector<que>w2[maxn];
int ans[maxn],lca[maxn];
void dfs1(int x,int f,int d){
    fa[x]=f;
    dep[x]=d;
    size[x]=1;
    for(int i=0;i<(int)mp[x].size();i++){
        int u=mp[x][i];
        if(u==f)continue;
        dfs1(u,x,d+1);
        size[x]+=size[u];
        if(!son[x]||size[son[x]]<size[u])son[x]=u;
    }
}
void dfs2(int x,int t){
    top[x]=t;
    if(!son[x])return ;
    dfs2(son[x],t);
    for(int i=0;i<(int)mp[x].size();i++){
        int u=mp[x][i];
        if(u==son[x]||u==fa[x])continue;
        dfs2(u,u);
    }
}
int LCA(int x,int y){
    int fx=top[x],fy=top[y];
    while(fx!=fy){
        if(dep[fx]>dep[fy])x=fa[fx];
        else y=fa[fy];
        fx=top[x];fy=top[y];
    }
    if(dep[x]>dep[y])return y;
    else return x;
}
int bac1[2*maxn],bac2[2*maxn];
void dfs(int x,int a,int b){
    for(int i=0;i<(int)w1[x].size();i++)
//    For(i,0,(int)w1[x].size()-1) //不强制转换会RE 
        bac1[w1[x][i].v+maxn]+=w1[x][i].tag;//更新桶内情况
    for(int i=0;i<(int)w2[x].size();i++)
//    For(i,0,(int)w2[x].size()-1) 
        bac2[w2[x][i].v+maxn]+=w2[x][i].tag;//更新桶内情况
   for(int i=0;i<(int)mp[x].size();i++){
        int u=mp[x][i];
        if(u==fa[x])continue;
        dfs(u,bac1[dep[u]+w[u]+maxn],bac2[w[u]-dep[u]+maxn]);
    }
    ans[x]+=bac1[dep[x]+w[x]+maxn]+bac2[w[x]-dep[x]+maxn]-a-b;//a,b是父亲节点的答案,要减去父亲节点的情况就是当前节点的结果
}
int main()
{
    n=read();m=read();
    For(i,1,n-1){
        int u=read(),v=read();
        mp[u].push_back(v);
        mp[v].push_back(u);
    }
    For(i,1,n)w[i]=read();
    For(i,1,m)s[i]=read(),t[i]=read();
    dfs1(1,0,0);
    dfs2(1,1);
    For(i,1,m)lca[i]=LCA(s[i],t[i]);
    For(i,1,m){
        if(lca[i]==t[i]){
            w1[s[i]].push_back((que){dep[s[i]],1});
            w1[fa[t[i]]].push_back((que){dep[s[i]],-1});
        }
        else if(lca[i]==s[i]){
            w2[t[i]].push_back((que){dep[s[i]]-2*dep[lca[i]],1});
            w2[fa[s[i]]].push_back((que){dep[s[i]]-2*dep[lca[i]],-1});
        }
        else{
            if(w[lca[i]]+dep[lca[i]]==dep[s[i]])--ans[lca[i]];
            w1[s[i]].push_back((que){dep[s[i]],1});
            w1[fa[lca[i]]].push_back((que){dep[s[i]],-1});
            w2[t[i]].push_back((que){dep[s[i]]-2*dep[lca[i]],1});
            w2[fa[lca[i]]].push_back((que){dep[s[i]]-2*dep[lca[i]],-1});
        }
    }
    dfs(1,0,0);
    For(i,1,n)printf("%d ",max(0,ans[i]));
    return 0;
}


















以上是关于bzoj4719: [Noip2016]天天爱跑步 树上差分的主要内容,如果未能解决你的问题,请参考以下文章

bzoj4719: [Noip2016]天天爱跑步 树上差分

BZOJ 4719 [Noip2016]天天爱跑步 ——树链剖分

BZOJ 4719--天天爱跑步(LCA&差分)

NOIP2016天天爱跑步

Noip 2016 天天爱跑步 题解

天天爱跑步[NOIP2016]