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

$ \rightarrow $ 戳我进洛谷原题** $ \rightarrow $ 戳我进BZOJ原题**

天天爱跑步

 

时空限制 \quad 2000ms / 512MB

题目描述

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

输入输出格式

输入格式

第一行有两个整数 $ n $ 和 $ m $ 。其中 $ n $ 代表树的结点数量, 同时也是观察员的数量, $ m $ 代表玩家的数量。

接下来 $ n−1 $ 行每行两个整数 $ u $ 和 $ v $ ,表示结点 $ u $ 到结点 $ v $ 有一条边。

接下来一行 $ n $ 个整数,其中第 $ j $ 个整数为 $ W_j $ , 表示结点 $ j $ 出现观察员的时间。

接下来 $ m $ 行,每行两个整数 $ S_i $ ,和 $ T_i $ ,表示一个玩家的起点和终点。

对于所有的数据,保证 $ 1≤Si,Ti≤n,0≤Wj≤n1\leq S_i,T_i\leq n, 0\leq W_j\leq n $ 。

输出格式

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

输入输出样例

输入样例#1

 6 3
 2 3
 1 2 
 1 4 
 4 5 
 4 6 
 0 2 5 1 2 3 
 1 5 
 1 3 
 2 6 

输出样例#1

 2 0 0 1 1 1 

输入样例#2

 5 3 
 1 2 
 2 3 
 2 4 
 1 5 
 0 1 0 3 0 
 3 1 
 1 4
 5 5 

输出样例#2

 1 2 1 0 1 

 

说明

【样例1说明】

对于 $ 1 $ 号点,$ W_i=0 $ ,故只有起点为1号点的玩家才会被观察到,
所以玩家 $ 1 $ 和玩家 $ 2 $ 被观察到,共有 $ 2 $ 人被观察到。

对于$ 2 $ 号点,没有玩家在第 $ 2 $ 秒时在此结点,共 $ 0 $ 人被观察到。

对于 $ 3 $ 号点,没有玩家在第 $ 5 $ 秒时在此结点,共 $ 0 $ 人被观察到。

对于 $ 4 $ 号点,玩家 $ 1 $ 被观察到,共 $ 1 $ 人被观察到。

对于 $ 5 $ 号点,玩家 $ 1 $ 被观察到,共 $ 1 $ 人被观察到。

对于 $ 6 $ 号点,玩家 $ 3 $ 被观察到,共 $ 1 $ 人被观察到。
 

【子任务】

每个测试点的数据规模及特点如下表所示。 提示: 数据范围的个位上的数字可以帮助判断是哪一种数据类型。

pic1

【提示】
如果你的程序需要用到较大的栈空问 (这通常意味着需要较深层数的递归),
请务必仔细阅读选手日录下的文本当rumung:/stact.p″,
以了解在最终评测时栈空问的限制与在当前工作环境下调整栈空问限制的方法。
 
在最终评测时,调用栈占用的空间大小不会有单独的限制,但在我们的工作环境中默认会有 $ 8MB $ 的限制。
这可能会引起函数调用层数较多时, 程序发生栈溢出崩溃。
 
我们可以使用一些方法修改调用栈的大小限制。 例如, 在终端中输入下列命令 ulimit -s 1048576
 
此命令的意义是,将调用栈的大小限制修改为 $ 1GB $
 
例如,在选手目录建立如下 sample.cpp 或 sample.pas

pic2

将上述源代码编译为可执行文件 sample 后,可以在终端中运行如下命令运行该程序
 
./sample
 
如果在没有使用命令“ ulimit -s 1048576”的情况下运行该程序, sample会因为栈溢出而崩溃;
如果使用了上述命令后运行该程序,该程序则不会崩溃。
 
特别地, 当你打开多个终端时, 它们并不会共享该命令, 你需要分别对它们运行该命令。
 
请注意, 调用栈占用的空间会计入总空间占用中, 和程序其他部分占用的内存共同受到内存限制。

 

题解

  • 列出恰好路过的条件并化简

  • 在 $ S_i $ 到 $ lca(S_i,T_i) $ 的阶段,应满足 $ d[S_i]=w[x]+d[x] $

  • 在 $ lca(S_i,T_i) $ 到 $ T_i $ 阶段,应满足 $ d[S_i]-2*d[lca(S_i,T_i)]=w[x]-d[x] $

  • 相当于在 $ S_i $ 位置出现一个 $ A $ 类数 $ d[S_i] $ ,在 $ lca(S_i,T_i) $ 的父节点消失

  • 在 $ T_i $ 位置出现一个 $ B $ 类数 $ d[S_i]-2*d[lca(S_i,T_i)] $ ,在 $ lca(S_i,T_i) $ 消失

  • 求子树 $ x $ 中,等于 $ w[x]+d[x] $ 的 $ A $ 类数个数+等于 $ w[x]-d[x] $ 的 $ B $ 类数个数

  • 全局数组计数,统计遍历子树x前后相应位置上的差
     

  • 我们使用两个全局的桶数组(因为这代表了 $ S_i-lca(S_i,T_i) $ 和 $ lca(S_i,T_i)-T_i $ 的两端)
    来保存 $ d[S_i] $ 和 $ d[S_i]-2*d[lca(S_i,T_i)] $ 的次数

  • 在递归进入一个节点 $ u $ 时,我们先记录下原本满足条件的个数,最后差分操作完成后用新增的个数减去原来的个数,就是新产生的答案数

  • 注意在 $ B $ 类数 $ w[x]-d[x] $ 中可能出现负数的情况,桶数组要全体 $ +n $
     

代码

/**************************************************************
    Problem: 2124
    User: PotremZ
    Language: C++
    Result: Accepted
    Time:1176 ms
    Memory:1564 kb
****************************************************************/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define maxn 300010
#define ri register int
vector<int>G[maxn],a1[maxn],a2[maxn],b1[maxn],b2[maxn];
int n,m,ans[maxn],dep[maxn],fa[maxn][21],sum1[maxn],sum2[maxn<<1],w[maxn];
void dfs(int u){
    for(ri v,i=0;i<G[u].size();++i)
        if(G[u][i]!=fa[u][0]){
            v=G[u][i];
            dep[v]=dep[u]+1; fa[v][0]=u;
            for(int i=1;(1<<i)<=dep[v];++i) fa[v][i]=fa[fa[v][i-1]][i-1];
            dfs(v);
        }
}
inline int lca(int u,int v){
    if(dep[u]>dep[v]) swap(u,v);
    for(ri i=20;~i;--i)
        if(dep[u]<=dep[v]-(1<<i)) v=fa[v][i];
    if(u==v) return u;
    for(ri i=20;~i;--i)
        if(fa[u][i]!=fa[v][i]){ u=fa[u][i]; v=fa[v][i]; }
    return fa[u][0];
}
void dfs2(int u){
    /记录原本的个数
    int tmp1=sum1[dep[u]+w[u]],tmp2=sum2[w[u]-dep[u]+n];
    for(ri i=0;i<G[u].size();++i)
        if(G[u][i]!=fa[u][0]) dfs2(G[u][i]);
    for(int i=0;i<a1[u].size();++i) ++sum1[a1[u][i]];
    for(int i=0;i<a2[u].size();++i) --sum1[a2[u][i]];
    for(int i=0;i<b1[u].size();++i) ++sum2[b1[u][i]+n];
    for(int i=0;i<b2[u].size();++i) --sum2[b2[u][i]+n];
    ans[u]+=sum1[dep[u]+w[u]]-tmp1+sum2[w[u]-dep[u]+n]-tmp2;
    //差分后子树u的答案增加了新满足条件的数的个数
}
int main(){
    scanf("%d %d",&n,&m);
    for(ri i=1;i<n;++i){
        int u,v;
        scanf("%d %d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1);
    for(ri i=1;i<=n;++i) scanf("%d",&w[i]);
    for(ri i=1;i<=m;++i){
        int s,t;
        scanf("%d %d",&s,&t);
        int LCA=lca(s,t);
        int g=dep[s]-2*dep[LCA];
        a1[s].push_back(dep[s]);
        a2[fa[LCA][0]].push_back(dep[s]);
        b1[t].push_back(g);
        b2[LCA].push_back(g);
        //记录A类数和B类数出现和消失的位置
    }
    dfs2(1);
    for(int i=1;i<=n;++i) printf("%d ",ans[i]);
    return 0;
}
posted @ 2018-09-08 20:58  potrem  阅读(400)  评论(0编辑  收藏  举报