P1600 NOIP2016 天天爱跑步 LCA + 树上差分

一、内容

题目描述

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

这个游戏的地图可以看作一一棵包含 nnn 个结点和 n−1n-1n−1 条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从 111 到 nnn 的连续正整数。

现在有 mmm 个玩家,第 iii 个玩家的起点为 sis_isi​,终点为 tit_iti​。每天打卡任务开始时,所有玩家在第 000 秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树,所以每个人的路径是唯一的)

小c 想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。在结点 jjj 的观察员会选择在第 wjw_jwj​ 秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第 wjw_jwj​ 秒也理到达了结点 jjj 。小c 想知道每个观察员会观察到多少人?

注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一 段时间后再被观察员观察到。 即对于把结点 jjj 作为终点的玩家:若他在第 wjw_jwj​ 秒前到达终点,则在结点 jjj 的观察员不能观察到该玩家;若他正好在第 wjw_jwj​ 秒到达终点,则在结点 jjj 的观察员可以观察到这个玩家。

输入格式

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

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

接下来一行 nnn 个整数,其中第 jjj 个整数为 wjw_jwj​ , 表示结点 jjj 出现观察员的时间。

接下来 mmm 行,每行两个整数 sis_isi​,和 tit_iti​,表示一个玩家的起点和终点。

对于所有的数据,保证 1≤si,ti≤n,0≤wj≤n1\leq s_i,t_i\leq n, 0\leq w_j\leq n1≤si​,ti​≤n,0≤wj​≤n。
输出格式

输出 111 行 nnn 个整数,第 jjj 个整数表示结点 jjj 的观察员可以观察到多少人。

输入 #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 

输出 #1

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条路径分别求解, 最终所有的人数便是这个点能观察到的人数。

三、代码

#include <cstdio>
#include <cstring>
#include <queue>
#include <iostream>
#include <vector>
#include <cmath> 
using namespace std;
const int N = 3e5 + 5, M = N * 2;
struct E {int v, next;} e[M];
struct Node {
	int ds, st; //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;
} 
发布了414 篇原创文章 · 获赞 380 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_41280600/article/details/104280302