刷题:天天爱跑步

93 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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;
}