Problem

Description

小 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\) 的观察员可以观察到这个玩家。

Input Format

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

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

接下来一行 \(n\) 个整数,其中第 \(i\) 个整数为 \(W_i\),表示结点 \(i\) 出现观察员的时间。

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

对于所有的数据,保证 \(1 \leq S_i, T_i \leq n, 0 \leq W_j \leq n\)。

Output Format

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

Sample

Input 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

Output 1

2 0 0 1 1 1

Input 2

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

Output 2

1 2 1 0 1

Explanation

Explanation for Input 1

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

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

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

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

对于 \(5\) 号点,玩家 \(2\) 被观察到,共 \(1\) 人被观察到。

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

Range

测试点 \(1 \sim 2\):\(n = m = 991\),所有人的起点等于自己的终点,即 \(S_i = T_i\);

测试点 \(3 \sim 4\):\(n = m = 992\),\(W_j = 0\);

测试点 \(5\):\(n = m = 993\);

测试点 \(6 \sim 8\):\(n = m = 99994\),树退化成一条链,对于 \(1 \leq i < n\),\(i\) 与 \(i + 1\) 有边;

测试点 \(9 \sim 12\):\(n = m = 99995\),\(S_i = 1\);

测试点 \(13 \sim 16\):\(n = m = 99996\),\(T_i = 1\);

测试点 \(17 \sim 19\):\(n = m = 99997\);

测试点 \(20\):\(n = m = 299998\)。

考点

\(LCA\) ,桶 。

思路

首先是看看数据范围的时候,嗯 ...... 讲真 \(3e5\) 的数据令人一下想到我们必须用 \(O(nlog_2n)\) 或者 \(O(n)\) 级别的算法或数据结构维护答案,当然,有树上路径的题目就会有 \(LCA\) ,那么 \(log\) 是必定有的了 = =。

接着我们看看询问们,\(emm\) ...... 树上统计符合一定距离要求的其他点,这很显然就是用桶来计数就对了。

那么我们来考虑所谓的用桶计数。用桶计数的关键点是:找到询问与当前统计点的等式。

那么我们来思考一下,对于每个询问,它和统计点的等式是什么呢 ?很显然,如果这条路径经过统计点,且起点与统计点距离等于统计点的 \(w\),那么这个询问对统计点的贡献就是 \(1\)。

但是这个等式并不具有普遍性,因为统计点相对于每个路径的起点都是不方便快速计算的,这样是 \(n^2\) 的。那么我们想想还有什么办法?深度啊!对于两点之间的距离不是可以利用深度表示吗?

那么我们来考虑关于深度的等式。这个当然分为两种情况:统计点在 \(S\) 到 \(lca\) 的路径上与统计点在 \(lca\) 到 \(T\) 的路径上。设统计点为点 \(i\) ,那么接着我们来列出具体等式:

对于情况一,由于 \(S\) 到 \(lca\) 必定是一条向上的路径且 \(i\) 被经过,则 \(i\) 必定为 \(S\) 的祖先节点,\(i\) 到 \(S\) 的距离必定为 \(deep[S]-deep[i]\) ,加上 \(w[i]\) 的观察时间,那么就是:\(deep[S]-deep[i]=w[i]\) ,由此移项可得等式一:

\[deep[i]+w[i]=deep[S]
\]

对于情况二,由于 \(lca\) 到 \(T\) 必定是一条向下的路径且 \(i\) 被经过,则 \(lca\) 必定为 \(i\) 的祖先节点,\(i\) 到 \(S\) 的距离必定为 \(S\) 到 \(lca\) 的距离加上 \(i\) 到 \(lca\) 的距离。那么 \(i\) 到 \(S\) 的距离式则为:\(deep[i]-deep[lca]+deep[S]-deep[lca]\) ,加上 \(w[i]\) 的观察时间等于距离,移项可得等式二:

\[deep[i]-w[i]=2*deep[lca]-deep[S]
\]

通过这两个等式,我们分开了节点 \(i​\) 与路径本身,那么询问对 \(i​\) 并没有本质的变量影响,接着我们只需要统计即可:

首先把每个询问从 \(lca​\) 处断开,分为 \(S-lca​\) 与 \(lca-T​\) 两种路径。

接着我们开始按 \(dfs\) 的顺序统计,由于一条路径贡献的点数有限,那么我们考虑如何计算贡献。对于两种路径,当我们向下走到某一个节点,它是某些路径处于下面的那个端点的时候,我们加上它们的贡献,而当我们离开某个节点,先统计当前节点答案,接着如果它是某些路径处于上面那个端点,我们减去它的贡献。

那么如何加减贡献与计算贡献呢?还记得之前的两个等式吗?对于一个点的统计答案,我们只需要寻找下标为 \(deep[i]-w[i]​\) 与下标为 \(deep[i]+w[i]​\) 的桶为它做出的贡献嘛。那么加减路径贡献就更简单了,如果到达路径的下方端,如果是第一种路径,那么把桶中下标为 \(deep[S]​\) 的位置 \(++​\) ,如果是第二种,那么把桶中下标为 \(2*deep[lca]-deep[S]​\) 的位置 \(++​\) ,离开路径的上方端时将对应位置 \(--​\) 即可。

但是很显然,对于祖先里有路径下方端的点,直接统计相关桶肯定会多余计算,我们不能统计完就清空桶,这样还是会超时,那么怎么办呢?简单,我们不能清空桶,我们可以把自己减去这个多余的贡献嘛。对于一个点,我们到达它的时候记录当前对应的桶内值,当遍历完子节点回到这个点的时候将现在的贡献见之前的贡献即可。

注意:若一条路径对 \(LCA\) 有贡献,那么先在 \(LCA\) 上减去这个贡献,因为拆成两条路径后会重复计算。

代码

#include <cstdio>
#include <iostream>
using namespace std;
int n, m, S, T, w[300001], head[300001], nx[600001], to[600001];
int size[300001], deep[300001], son[300001], fa[300001], top[300001],
cir = 100000; // cir做为桶下标的偏移程度,以防 2*deep[lca]-deep[S] 是个负数
int tag1[1000001], tag2[1000001], Ans[300001]; //第一种、第二种路径的桶
int hd1[300001], hd2[300001], nx1[600001], nx2[600001], to1[600001],
to2[600001]; //询问路径挂载
int cnt, sum1, sum2;
struct Pro {
int t1, t2;
} k[600001]; //询问拆成的两条路径对应桶的下标,如果是第一种那么t2=0,反之t1=0
void read(int &x) {
x = 0;
char ch = getchar();
while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) {
x = x * 10 + ch - '0';
ch = getchar();
}
}
void add(int u, int v, int d) {
to[d] = v, nx[d] = head[u];
head[u] = d;
}
void build1(int x) {
size[x] = 1;
top[x] = x;
for (int i = head[x]; i; i = nx[i])
if (to[i] != fa[x]) {
fa[to[i]] = x;
deep[to[i]] = deep[x] + 1;
build1(to[i]);
size[x] += size[to[i]];
if (size[to[i]] > size[son[x]]) son[x] = to[i];
}
}
void build2(int x) {
if (son[x]) {
top[son[x]] = top[x];
build2(son[x]);
}
for (int i = head[x]; i; i = nx[i])
if (to[i] != fa[x] && to[i] != son[x]) build2(to[i]);
} //重链划分
int LCA(int u, int v) {
while (top[u] != top[v]) {
if (deep[top[u]] < deep[top[v]]) swap(u, v);
u = fa[top[u]];
}
if (deep[u] > deep[v]) swap(u, v);
return u;
} //树链LCA
void Ad(int t1, int t2, int d) { k[d].t1 = t1, k[d].t2 = t2; } //添加询问
void Ad1(int u, int d) {
sum1++;
to1[sum1] = d, nx1[sum1] = hd1[u], hd1[u] = sum1;
}
void Ad2(int u, int d) {
sum2++;
to2[sum2] = d, nx2[sum2] = hd2[u], hd2[u] = sum2;
} //将询问挂载到节点上,第一个为询问加上贡献的位置的挂载,第二个为询问减去贡献的位置的挂载。
void Count(int x) {
int pos1 = deep[x] + w[x] + cir, pos2 = deep[x] - w[x] + cir;
int now1 = tag1[pos1], now2 = tag2[pos2];
for (int i = hd1[x]; i; i = nx1[i]) {
tag1[k[to1[i]].t1]++;
tag2[k[to1[i]].t2]++;
}
for (int i = head[x]; i; i = nx[i])
if (to[i] != fa[x]) Count(to[i]);
Ans[x] += tag1[pos1] - now1 + tag2[pos2] - now2;
for (int i = hd2[x]; i; i = nx2[i]) {
tag1[k[to2[i]].t1]--;
tag2[k[to2[i]].t2]--;
}
} //最后统计答案
int main() {
read(n), read(m);
int u, v;
for (int i = 1; i < n; i++) {
read(u), read(v);
add(u, v, i);
add(v, u, i + n);
}
for (int i = 1; i <= n; i++) read(w[i]);
deep[1] = 1;
build1(1);
build2(1);
while (m--) {
read(S), read(T);
int lca = LCA(S, T);
Ad(deep[S] + cir, 0, ++cnt);
Ad1(S, cnt);
Ad2(lca, cnt);
Ad(0, deep[lca] * 2 - deep[S] + cir, ++cnt);
Ad1(T, cnt);
Ad2(lca, cnt);
if (deep[lca] + w[lca] == deep[S]) Ans[lca]--; //减去重复贡献
} //拆分、添加、挂载询问
Count(1);
for (int i = 1; i <= n; i++) printf("%d ", Ans[i]);
}

【NOIP 2016】Day1 T2 天天爱跑步的更多相关文章

  1. [NOIP2016 DAY1 T2]天天爱跑步-[差分+线段树合并][解题报告]

    [NOIP2016 DAY1 T2]天天爱跑步 题面: B[NOIP2016 DAY1]天天爱跑步 时间限制 : - MS 空间限制 : 565536 KB 评测说明 : 2s Description ...

  2. 【NOIP2016】DAY1 T2 天天爱跑步

    [NOIP2016]DAY1 T2 天天爱跑步 Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.?天天爱跑步?是一个养成类游戏,需要玩家每天按时 ...

  3. Luogu P1600[NOIP2016]day1 T2天天爱跑步

    号称是noip2016最恶心的题 基本上用了一天来搞明白+给sy讲明白(可能还没讲明白 具体思路是真的不想写了(快吐了 如果要看,参见洛谷P1600 天天爱跑步--题解 虽然这样不好但我真的不想写了 ...

  4. NOIP2016 DAY1 T2天天爱跑步

    传送门 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.«天天爱跑步»是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵包含 ...

  5. NOIP2016 Day1 T2 天天爱跑步(树上差分,LCA)

    原文链接 原题链接 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏 ...

  6. NOIP提高组2016 D1T2 【天天爱跑步】

    码了一个下午加一个晚上吧...... 题目描述: 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成 ...

  7. Noip 2016 Day1 题解

    老师让我们刷历年真题, 然后漫不经心的说了一句:“你们就先做做noip2016 day1 吧” ...... 我还能说什么,,,,,老师你这是明摆着伤害我们啊2333333333 预计分数:100+2 ...

  8. 3728 联合权值[NOIP 2014 Day1 T2]

    来源:NOIP2014 Day1 T2 OJ链接: http://codevs.cn/problem/3728/ https://www.luogu.org/problemnew/show/P1351 ...

  9. [NOIP 2016D2T2/Luogu P1600] 天天爱跑步 (LCA+差分)

    待填坑 Code //Luogu P1600 天天爱跑步 //Apr,4th,2018 //树上差分+LCA #include<iostream> #include<cstdio&g ...

随机推荐

  1. tensorflow--variable_scope

    1.with tf.variable_scope(name , reuse = reuse) (1)创建variable scope with tf.variable_scope("foo& ...

  2. C# 自定义承载控件

    例如ToolStripTextBox类,里面的封装就是一个TextBox控件..NET的类库提供了ToolStripComBox和ToolStripProgressBar两个类.这些类都是从ToolS ...

  3. SpringMVC中的自定义参数绑定案例

    由于日期数据有很多种格式,所以springmvc没办法把字符串转换成日期类型.所以需要自定义参数绑定.前端控制器接收到请求后,找到注解形式的处理器适配器,对RequestMapping标记的方法进行适 ...

  4. 深入剖析Kubernetes k8s

    深入剖析Kubernetes k8s 毫无疑问,Kubernetes 已经成为容器领域当之无愧的事实标准.除了 Google.Microsoft 等技术巨擘们在容器领域里多年的博弈外,国内的 BAT. ...

  5. 搭建Vue2+Vuex+Webpack+Pug(jade)+Stylus环境

     一.开发环境配置 开始之前,假设你已经安装了最新版本的 node 和 npm. 全局安装 vue-cli 和 webpack : npm install vue-cli webpack -g 创建工 ...

  6. The Little Prince-12/17

    The Little Prince-12/17 今年四六级吐槽,说到做到!!!文思泉涌了兄弟们! “这就是你自己的错了,我本来并不想给你带来任何痛苦的,可你却要我驯服你……可你要哭了,可你什么好处也没 ...

  7. sqlalchemy多对多查询

    # coding:utf-8 from sqlalchemy import create_engine, Column, String, Integer, ForeignKey, Table from ...

  8. Docker MySQL5.5镜像

    定制MySQL的镜像有个很大的难题:mysqld启动之前要初始化数据目录,5.5自带有空账号密码需要初始化. Dockerfile FROM centos # 拷贝需要的安装和MySQL初始脚本 CO ...

  9. Program terminated with signal 6, Aborted,有可能啥原因呢?

    Program terminated with signal 6, Aborted,有可能啥原因呢?其中一种原因就是事实上的OOM(虽然/var/log/message中没有标明操作系统kill了进行 ...

  10. rabbitmq队列的exclusive,durability,auto-delete属性以及消息可靠传输设计

    非集群下,简单的说:- 如果是excl,则设置durability没有意义,因为不管服务器挂了还是客户端主动/被动断开了,队列都会自动删除.- auto-delete,其实可简单的认为是同理,即使非e ...