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

从标准输入读入数据。

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

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

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

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

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

Output

输出到标准输出。

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

Sample Input

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

Sample Output

2 0 0 1 1 1

Sample Explanation

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

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

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

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

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

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

HINT

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

测试点编号 $n$ $m$ 约定
1 $=991$ $=991$ 所有人的起点等于自己的终点,即 $S_i = T_i$
2
3 $=992$ $=992$ $W_j=0$
4
5 $=993$ $=993$
6 $=99994$ $=99994$ 树退化成一条链,其中 $1$ 与 $2$ 有边,$2$ 与 $3$ 有边,$\dots$,$n-1$ 与 $n$ 有边
7
8
9 $=99995$ $=99995$ 所有的 $S_i=1$
10
11
12
13 $=99996$ $=99996$ 所有的 $T_i=1$
14
15
16
17 $=99997$ $=99997$
18
19
20 $=299998$ $=299998$

时间限制:$2\texttt{s}$

空间限制:$512\texttt{MB}$

题解

$O(n^3)$算法

1、容易得出$O(n^3)$最裸的暴力算法,然而。。。

$O(n^2)$算法

1、$O(n^2)$算法是枚举每个玩家和观察员,然后直接判断能不能观察到;

2、首先观察员必须要在玩家的路上,还要得出观察员离玩家多远才能直接判断;

3、一个结论是如果起点在$S$,终点在$T$,观察员在$A$点,那么$A$在$S→T$上当且仅当$Dis(S,T)=Dis(S,A)+Dis(A,T)$,那么就只需要解决求距离的问题了;

4、以每个点为根节点$DFS$整棵树就可以方便地预处理出两点之间的距离了。

继续优化

1、对于枚举已经没有优化的空间了,我们来优化判断的过程:对于玩家$i$和观察员$j$,$j$能观察到$i$当且仅当$j$在$i$的路径上且$Dis(i,j)=W(j)$;

2、假定指定一个点为树根之后,如果$i$和$j$互为祖孙关系,那么还可以写成:

$$|Hi-Hj|=Wj(Hi为i这个点离树根的距离)$$

那么只需要

$$(+/-)Hi=Wj(+/-)Hj$$

就可以了。

继续优化

1、那么我们就可以把$Wj(+/-)Hj$设为观察员的特征值,同一特征值的所有点(包括玩家)只要满足一个点在另一个点路径上就可以都算到答案里面了;

2、我们做一个转化:一条在树上的路径一定是先往上走一段再往下走一段的,所以我们按最高点分成左右两条路径分别算答案;

3、这样就当做分成两个玩家,一个玩家起点与之前相同但是终点在最高点处,另一个玩家起点在最高点并且出现时间接着上一个玩家的到达时间;

4、这样就可以按照我们之前互为祖孙关系的做法做了。

5、往上的半个玩家我们就算其特征值为$Hi$,另外半个假如最高点是$k$,那么特征值为$Dis(i,k)-Hk$。当然$Wj+/-Hj$的正负号也对应了玩家往上往下走,所以我们向上向下两种情况做两遍;

6、但怎么快速地将路径上同一特征值所有观察员答案加一呢?

7、我们注意到路径都是互为祖孙关系的,可以联想到序列上的前缀和,那么树上的前缀和就是每个点的值往上加到父亲上;

8、我们对每半个玩家在其起点终点处打上$+1$或$-1$的标记,其中较低点为$+1$、较高点为$-1$,然后$DFS$一遍,将标记加到前缀和加到父亲处,答案为当前点的前缀和;

9、类比数组上的前缀和可以知道只会有这一段路径上的点答案会$+1$;

10、当然如果要对于某一个特征值有标记的话我们可以对于每个特征值都建一棵树,方法是$DFS$一遍,记录从根到当前点每个特征值的最近祖先,然后重置当前点的父亲为最近同特征祖先;

11、复杂度主要花在分离一个玩家上,需要找到$S$和$T$的最高点,可以用树链剖分,常数比较小,或者$Tarjan$。

 //It is made by Awson on 2017.10.22
#include <set>
#include <map>
#include <cmath>
#include <ctime>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
#define Min(a, b) ((a) < (b) ? (a) : (b))
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define sqr(x) ((x)*(x))
using namespace std;
const int N = ; struct tt {
int to, next, id;
}edge[(N<<)+], query[(N<<)+];
int pathe[N+], tope, pathq[N+], topq;
int n, m, u, v;
int w[N+], s[N+], tt[N+];
int dep[N+], fa[N+], lca[N+];
int mark[N+];
vector<int>q1[N+], q2[N+], q3[N+];
int cnt1[(N<<)+], cnt2[(N<<)+], ans[N+]; int find(int r) {
return fa[r] ? fa[r] = find(fa[r]) : r;
}
void add(int u, int v) {
edge[++tope].to = v;
edge[tope].next = pathe[u];
pathe[u] = tope;
}
void add2(int s, int t, int id) {
query[++topq].to = t;
query[topq].id = id;
query[topq].next = pathq[s];
pathq[s] = topq;
}
void tarjan(int u, int depth) {
dep[u] = depth;
for (int i = pathe[u]; i; i = edge[i].next)
if (!dep[edge[i].to]) {
tarjan(edge[i].to, depth+);
fa[edge[i].to] = u;
}
for (int i = pathq[u]; i; i = query[i].next) if (dep[query[i].to]) lca[query[i].id] = find(query[i].to);
}
void dfs1(int u, int fa) {
int tmp = w[u]+dep[u];
int pre = cnt1[tmp];
for (int i = pathe[u]; i; i = edge[i].next)
if (edge[i].to != fa) dfs1(edge[i].to, u);
cnt1[dep[u]] += mark[u];
ans[u] += cnt1[tmp]-pre;
for (int i = ; i < q1[u].size(); i++) cnt1[q1[u][i]]--;
}
void dfs2(int u, int fa) {
int tmp = w[u]-dep[u]+n;
int pre = cnt2[tmp];
for (int i = pathe[u]; i; i = edge[i].next)
if (edge[i].to != fa) dfs2(edge[i].to, u);
for (int i = ; i < q2[u].size(); i++) cnt2[q2[u][i]]++;
ans[u] += cnt2[tmp]-pre;
for (int i = ; i < q3[u].size(); i++) cnt2[q3[u][i]]--;
}
void work() {
scanf("%d%d", &n, &m);
for (int i = ; i < n; i++) {
scanf("%d%d", &u, &v);
add(u, v); add(v, u);
}
for (int i = ; i <= n; i++) scanf("%d", &w[i]);
for (int i = ; i <= m; i++) {
scanf("%d%d", &s[i], &tt[i]);
add2(s[i], tt[i], i); add2(tt[i], s[i], i);
}
tarjan(, );
for (int i = ; i <= m; i++) {
mark[s[i]]++; q1[lca[i]].push_back(dep[s[i]]);
}
dfs1(, );
for (int i = ; i <= m; i++) {
int l = dep[s[i]]+dep[tt[i]]-*dep[lca[i]];
q2[tt[i]].push_back(l-dep[tt[i]]+n);
q3[lca[i]].push_back(l-dep[tt[i]]+n);
}
dfs2(, );
for (int i = ; i <= m; i++) if (w[lca[i]]+dep[lca[i]] == dep[s[i]]) ans[lca[i]]--;
for (int i = ; i <= n; i++) printf("%d ", ans[i]);
}
int main() {
work();
return ;
}

[NOIp 2016]天天爱跑步的更多相关文章

  1. [NOIP]2016天天爱跑步

    [NOIP]2016天天爱跑步 标签: LCA 树上差分 NOIP Description 小C同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是 ...

  2. NOIP 2016 天天爱跑步 80分暴力

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

  3. Noip 2016 天天爱跑步 题解

    [NOIP2016]天天爱跑步 时间限制:2 s   内存限制:512 MB [题目描述] 小C同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是 ...

  4. NOIP 2016 天天爱跑步 (luogu 1600 & uoj 261) - 线段树

    题目传送门 传送点I 传送点II 题目大意 (此题目不需要大意,我认为它已经很简洁了) 显然线段树合并(我也不知道哪来这么多显然) 考虑将每条路径拆成两条路径 s -> lca 和 t -> ...

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

    [题意]n个点的树,有m个人同时开始走链,每一步花一秒,n个点都有观察员在ai秒观察,求每个观察员观察到的人数. [算法]树上差分(主席树||线段树合并) [题解]一个人的走链可以拆成u-lca和lc ...

  6. [luogu]P1600 天天爱跑步[LCA]

    [luogu]P1600 [NOIP 2016]天天爱跑步 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.«天天爱跑步»是一个养成类游戏,需要玩家每天按时上 ...

  7. 【NOIP 2016】Day1 T2 天天爱跑步

    Problem Description 小 C 同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任 ...

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

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

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

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

随机推荐

  1. 记录python接口自动化测试--requests使用和基本方法封装(第一目)

    之前学习了使用jmeter+ant做接口测试,并实现了接口的批量维护管理(大概500多条用例),对"接口"以及"接口测试"有了一个基础了解,最近找了一些用pyt ...

  2. 【Alpha版本】冲刺阶段 - Day6 - 乘风

    今日进展 袁逸灏:1.实现了碰撞的判定:2.代码规范化:3.解决了项目基本代码.(7h) 刘伟康:补充了上次未完成的任务,即检查代码规范,增加AS规范并整理上传至码云.除此之外,学习了部分 Andro ...

  3. 实验四:Android 开发基础

    实验四:实验报告 课程:程序设计与数据结构 班级: 1623 姓名: 张旭升 学号:20162329 指导教师:娄嘉鹏 王志强 实验日期:5月26日 实验密级: 非密级 预习程度: 已预习 必修/选修 ...

  4. 我所知道的window.location

    多说无益 直接上干货 假如一个地址为  http://127.0.0.1:5000/index.html?id=4 window.location.href -- 完整路径 -- http://127 ...

  5. Python扩展模块——自动化(testlinkAPI的使用)

    使用TESTLINKAPI首先要安装TestLink_API_Python_client-0.6.4(当前最新版本) 目前只使用到了通过api获取testlink中的自定义字段and值 url = ' ...

  6. Java+Maven+selenium+testing+reportNG自动化测试框架

    最近公司新出了一个产品,需要搭建自动化测试框架,这是一个学以至用的好机会,跟上级申请后,决定搭建一个java自动化测试框架. Java自动化测试对我来讲可以说不难不易,因为java是我大学在校四年学的 ...

  7. 深度爬取之rules

    深度爬取之rules CrawlSpider使用rules来决定爬虫的爬取规则,并将匹配后的url请求提交给引擎.所以在正常情况下,CrawlSpider不需要单独手动返回请求了. 在rules中包含 ...

  8. python生成单词壁纸

    1.首先上结果: 其实就是一段简单的代码.加上英语单词表加上几张背景图生成许多类似的图片再设置成桌面背景,十分钟一换.有心的人闲的时候随手就能换换桌面背背单词.最不济也能混个脸熟. 3.上代码 #-* ...

  9. LDAP apacheds解决方案

    Apache DS 配置与管理   LADP基本介绍 LDAP(轻量级目录访问协议)以目录的形式来管理资源(域用户,用户组,地址簿,邮件用户,打印机等等).   特点: 1. LDAP是一种网略协议而 ...

  10. python常用运算符

    1. / 浮点除法,就算分子分母都是int类型,也返回float类型,比如我们用4/2,返回2.0 2. // 整数除法,根据分子分母的不同组合,返回的值有差异. 正数//正数,取整,比如5//3,返 ...