[NOIp 2016]天天爱跑步
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
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
Sample Output
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]天天爱跑步的更多相关文章
- [NOIP]2016天天爱跑步
[NOIP]2016天天爱跑步 标签: LCA 树上差分 NOIP Description 小C同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是 ...
- NOIP 2016 天天爱跑步 80分暴力
题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.«天天爱跑步»是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图可以看作一一棵包含 个结点 ...
- Noip 2016 天天爱跑步 题解
[NOIP2016]天天爱跑步 时间限制:2 s 内存限制:512 MB [题目描述] 小C同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是 ...
- NOIP 2016 天天爱跑步 (luogu 1600 & uoj 261) - 线段树
题目传送门 传送点I 传送点II 题目大意 (此题目不需要大意,我认为它已经很简洁了) 显然线段树合并(我也不知道哪来这么多显然) 考虑将每条路径拆成两条路径 s -> lca 和 t -> ...
- 【NOIP】提高组2016 天天爱跑步
[题意]n个点的树,有m个人同时开始走链,每一步花一秒,n个点都有观察员在ai秒观察,求每个观察员观察到的人数. [算法]树上差分(主席树||线段树合并) [题解]一个人的走链可以拆成u-lca和lc ...
- [luogu]P1600 天天爱跑步[LCA]
[luogu]P1600 [NOIP 2016]天天爱跑步 题目描述 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.«天天爱跑步»是一个养成类游戏,需要玩家每天按时上 ...
- 【NOIP 2016】Day1 T2 天天爱跑步
Problem Description 小 C 同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任 ...
- NOIP提高组2016 D1T2 【天天爱跑步】
码了一个下午加一个晚上吧...... 题目描述: 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成 ...
- [NOIP 2016D2T2/Luogu P1600] 天天爱跑步 (LCA+差分)
待填坑 Code //Luogu P1600 天天爱跑步 //Apr,4th,2018 //树上差分+LCA #include<iostream> #include<cstdio&g ...
随机推荐
- java Classpath 的解读
在了解java的classpath之前先来看看java的运行机制 1.首先是编译,将.java文件编译成虚拟机认识的二进制文件.这个过程需要的命令是javac 可以在jdk的bin目录中找到,ja ...
- C 连接mysql VC的步骤
初学C,看到C 连接mysql的教程不是很多,遇到很多的问题,看过许多盟友的解决方法,有点模糊(对我这个菜鸟来说),下面贴出具体步骤,一起学习: 1.C连接mysql的方法:C ,C ++ ,ODBC ...
- gitignore忽略规则
我们用git提交本地代码时,有些文件或日志是不需要提交的,这个时候可以用.gitignore来解决这个问题: 首先,我们需要创建一个.gitignore文件,用命令输入 touch .gitignor ...
- Beta冲刺集合
1.Day1 http://www.cnblogs.com/bugLoser/p/8075868.html 2.Day2 http://www.cnblogs.com/bugLoser/p/80758 ...
- Android开发简易教程
Android开发简易教程 Android 开发因为涉及到代码编辑.UI 布局.打包等工序,有一款好用的IDE非常重要.Google 最早提供了基于 Eclipse 的 ADT 作为开发工具,后来在2 ...
- js 点击 返回顶部 动画
附上效果图 触发前 触发后 HTML代码: CSS代码 JS代码 由于复制文本太丑了 所以直接放的图片 但是我在评论区把js代码又复制了一边 以便你们使用
- Python——cmd调用(os.system阻塞处理)
os.system(返回值为0,1,2) 0:成功 1:失败 2:错误 os.system默认阻塞当前程序执行,在cmd命令前加入start可不阻塞当前程序执行. 例如: import os os.s ...
- TCP/IP和HTTP协议代理
TCP/IP协议族 TCP/IP(传输控制协议/网际协议)是用于计算机通信的一个协议族. TCP/IP协议族包括诸如Internet协议(IP).地址解析协议(ARP).互联网控制信息协议(ICMP) ...
- kafka---broker 保存消息
1 .存储方式 物理上把 topic 分成一个或多个 patition(对应 server.properties 中的 num.partitions=3 配置),每个 patition 物理上对应一个 ...
- oracle11g导出表时会发现少表,空表导不出解决方案
oracle11g导出表时会发现少表,空表导不出解决方案. 一:背景引入 oracle11g用exp命令导出数据库表时,有时会发现只导出了一部分表时而且不会报错,原因是有空表没有进行导出,之前一直 ...