持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情
题目来源:NOIP2016提高组
题目描述:
小 C 同学认为跑步非常有趣,于是决定制作一款叫作《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一棵包含 n 个节点和 n−1 条边的树,任意两个节点存在一条路径互相可达。树上节点的编号是 1∼n 之间的连续正整数。
现在有 m 个玩家,第 i 个玩家的起点为 Si,终点为 Ti。
每天打卡任务开始时,所有玩家在第 0 秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。因为地图是一棵树,所以每个人的路径是唯一的。
小 C 想知道游戏的活跃度,所以在每个节点上都放置了一个观察员。在节点 j 的观察员会选择在第 Wj 秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第 Wj 秒也正好到达了节点 j。小 C 想知道每个观察员会观察到多少人?
注意:我们认为一个玩家到达自己的终点后,该玩家就会结束游戏,他不能等待一段时间后再被观察员观察到。即对于把节点 j 作为终点的玩家:若他在第 Wj 秒前到达终点,则在节点 j 的观察员不能观察到该玩家;若他正好在第 Wj 秒到达终点,则在节点 j 的观察员可以观察到这个玩家。
输入格式
第一行有两个整数 n 和 m。
其中 n 代表树的结点数量,同时也是观察员的数量,m 代表玩家的数量。
接下来 n−1 行每行两个整数 U 和 V,表示结点 U 到结点 V 有一条边。
接下来一行 n 个整数,其中第个整数为 Wj,表示结点出现观察员的时间。
接下来 m 行,每行两个整数 Si 和 Ti,表示一个玩家的起点和终点。
输出格式
一行 n 个整数,第 i 个整数表示结点 i 的观察员可以观察到多少人。
输入样例:
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
输出样例:
2 0 0 1 1 1
思路分析:
- 每个玩家的路线由于是一定的,可以拆分为 s —>lca(s, t), lca(s, t)---->t 这2段路径。其中后者不包括lca(s,t)。
- 那么从观察员出发,它在某时刻能观察到人的条件:
- 点x在第一条路径上: dep[s] - dep[x] = w[x] (x为观察点的节点编号) 由于每秒钟会移动一次,那么代表深度增加或减少。s到x的时间就是 2者的深度之差,只要等于w[x]是观察员出现的时间,那么观察员就可以看到它。
- 同理点x在第二条路径上: dep[s] + dep[x] - 2 dep[lca] = w[x]。
- 现在以第一条路径出发: 可以转化为 dep[s] = dep[x] + w[x] 。 那么代表s到lca的路径上都增加一个dep[s]的物品, 最终求在这条路径上的x处 dep[x] + w[x]这样的物品数有多少个。
- 那么第二条路径上就是 lca到t的路径上所有点都增加一个 dep[s] - 2 * dep[lca]的物品, 最后求这条路径上x处 w[x] - dep[x] 的物品数有多少。
- 2条路径分别求解, 最终所有的人数便是这个点能观察到的人数。
AC代码:
#include <cstdio>
#include <cstring>
#include <queue>
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
const int N =600060, M = N * 2;
struct E {int v, next;} e[M];
struct Node {
int ds, st;
Node(int d, int s): ds(d), st(s){}
};
int n, m, u, v, len, lg, ans[N], h[N], w[N], f[N][20], c1[N * 2], c2[N * 2], dep[N];
vector<Node> p1[N], p2[N];
void add(int u, int v) {e[++len].v = v; e[len].next = h[u]; h[u] = len;}
void bfs() {
lg = int(log(n) / log(2)) + 1;
queue<int> q; q.push(1); dep[1] = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int j = h[u]; j; j = e[j].next) {
int v = e[j].v;
if (dep[v]) continue;
dep[v] = dep[u] + 1;
f[v][0] = u; q.push(v);
for (int k = 1; k <= lg; k++) f[v][k] = f[f[v][k - 1]][k - 1];
}
}
}
int LCA(int x, int y) {
if (dep[y] > dep[x]) swap(x, y);
for (int k = lg; k >= 0; k--) {
if (dep[f[x][k]] >= dep[y]) x = f[x][k];
}
if (x == y) return x;
for (int k = lg; k >= 0; k--) {
if (f[x][k] != f[y][k]) x = f[x][k], y = f[y][k];
}
return f[x][0];
}
void dfs(int u, int fa) {
int cnt1 = c1[w[u] + dep[u]], cnt2 = c2[w[u] - dep[u] + n];
for (int j = h[u]; j; j = e[j].next) {
int v = e[j].v;
if (v == fa) continue;
dfs(v, u);
}
//把v所有能加的都加上
for (int i = 0; i < p1[u].size(); i++) {
Node t = p1[u][i];
c1[t.ds] += t.st;
}
for (int i = 0; i < p2[u].size(); i++) {
Node t = p2[u][i];
c2[t.ds] += t.st;
}
ans[u] = c1[w[u] + dep[u]] - cnt1 + c2[w[u] - dep[u] + n] - cnt2;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i < n; i++) {
scanf("%d%d", &u, &v);
add(u, v); add(v, u);
}
bfs();
for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
for (int i = 1; i <= m; i++) {
scanf("%d%d", &u, &v);
int lca = LCA(u, v);
p1[u].push_back(Node(dep[u], 1));
p1[f[lca][0]].push_back(Node(dep[u], -1));
p2[v].push_back(Node(dep[u] - 2 * dep[lca] + n, 1));
p2[lca].push_back(Node(dep[u] - 2 * dep[lca] + n, -1));
}
dfs(1, 0);
for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
return 0;
}