2021牛客暑期多校训练营9 E. Eyjafjalla(树上倍增+DFS序+线段树)
链接:https://ac.nowcoder.com/acm/contest/11260/E
来源:牛客网
题目描述
There are n cities in the volcano country, numbered from 1 to n. The city 1 is the capital of the country, called Eyjafjalla. A large volcano is located in the capital, so the temperature there is quite high. The temperature of city i is denoted by titi.
The n cities are connected by n-1 undirected roads. The i-th road connects city uiui and city vivi. If uiui is closer to the capital than vivi, then tui>tvitui>tvi. The capital has the highest temperature.
The coronavirus is now spreading across the whole world. Although the volcano country is far away from the mess, Cuber QQ, the prime minister of the country, is still designing an emergency plan in case some city getting infected. Now he wants to know if a virus whose survival temperature range is [l, r] breaks out in city x, how many cities will be infected. The assumption is that, if the temperature of one city is between l and r inclusive, and it is connected to another infected city, it is infected too.
输入描述:
The first line contains an integer n (1≤n≤1051≤n≤105), representing the number of cities.
In each of the next n-1 lines, there are two integers u,v (1≤u,v≤n1≤u,v≤n, u≠vu=v) per line, denoting that a road connecting u and v.
The next line contains n space-separated integers t1,t2,…,tnt1,t2,…,tn (1≤ti≤1091≤ti≤109), representing the temperature in each city.
The next line contains one integer q (1≤q≤1051≤q≤105), representing the number of queries.
The next q lines contain one query each line. Each query is 3 space-separated integers x,l,r (1≤x≤n,1≤l≤r≤1091≤x≤n,1≤l≤r≤109), representing a query that a virus whose survival temperature range is [l, r] breaks out in city x.
输出描述:
For each query, output one integer representing the number of cities that will be infected. As a special case, if tx∉[l,r]tx∈/[l,r], please output 0.
示例1
输入
复制
4
1 2
1 3
2 4
10 8 7 6
3
1 10 10
2 6 7
3 7 10
输出
复制
1
0
3
比赛的时候想到了大致思路,但当时非常傻逼的以为没法用线段树求区间大于等于x的数的个数....
这个题的解法貌似有一万种,包括但不限于树上倍增 + 主席树、DSU + 树状数组、莫队、树剖 + 线段树......主席树还不怎么会,于是写了一个普通的线段树。
首先注意到这个题一个关键的信息就是从根节点到其他点的温度随着树上简单路径长度的增加其实是递减的。一种朴素的思路是对于每个询问中病毒的存活区间[l, r],找到温度不大于r的最靠近树根的点,然后求以这个点为根的子树里有多少点满足条件。那么就有两个问题,第一个问题是这个点怎么确定。因为要求这棵子树包含询问中的点x,直接暴力显然不可,那么不妨考虑树上倍增,这样预处理完后对于每次查询就能以\(O(logn)\)的时间复杂度找到满足要求的子树的树根了。第二个问题是找到满足条件的子树以后怎么求子树里温度大于等于询问中温度区间左端点l的点的个数。题解给的办法是主席树,或者注意到这实际上是静态的询问,可以先全部读入然后对所有数进行离散化以后再用各种办法做。一种比较简单的思路是借助树的dfs序,这样每棵子树对应的区间实际上是连续的,因此就把树上问题转化为对区间的询问。
具体来说,先求出树的dfs序,然后对于dfs序建线段树,线段树上维护区间最大值和最小值。对于每次查询,先通过树上倍增的方法找到温度不大于r的最靠近树根的点sbutr。然后查询\([dfn[subtr],dfn[subtr]+sz[subtr]-1]\)这个区间里有多少温度大于等于l的点(对一个点x,以x为根的子树的区间范围就是\([dfn[x],dfn[x]+sz[x]-1]\),其中sz数组为对应子树的大小)。具体线段树是怎么维护的可以看代码注释。可能有人会问会不会最后查询出来的一个个满足温度条件的子区间内部有相互不可达的情况,实际上这是不可能的。因为从根节点到树上的点,路径越长温度越低,筛掉的肯定是整棵树的末梢的点,不会出现一个点不满足条件而其子树的点满足条件的情况。
特别注意的是建树的时候的一句:
tree[p].mx = tree[p].mn = t[id[l]];
因为是对dfn建线段树,所以在求dfn数组(dfn[x]为x这个点对应的dfs序号)时也要顺便求出来id数组(id[x]表示x这个dfs序号对应的原始的点),这样在上面赋值的时候就能很方便的找到l这个dfs序号对应的原始的城市对应的温度了。一定注意这里不要写成t[dfn[l]]。
#include <bits/stdc++.h>
#define pb push_back
#define N 200005
#define pii pair
using namespace std;
int n, head[N], ver[2 * N], Next[2 * N], tot = 0;
int t[N];
void add(int x, int y) {
ver[++tot] = y, Next[tot] = head[x], head[x] = tot;
}
int f[N][35], d[N], dist[N], tt;
queue<int> q;
void bfs() {
q.push(1);
d[1] = 1;
while(q.size()) {
int x = q.front();
q.pop();
for(int i = head[x]; i; i = Next[i]) {
int y = ver[i];
if(d[y]) continue;
d[y] = d[x] + 1;
dist[y] = dist[x] + 1;
f[y][0] = x;
for(int j = 1; j <= tt; j++) {
f[y][j] = f[f[y][j - 1]][j - 1];
}
q.push(y);
}
}
}
int lg[N];
int gettop(int x, int up) {
int r = x, pre = x;
for(int i = tt; i >= 0; i--) {
if(t[f[r][i]] <= up && f[r][i]) {
r = f[r][i];
}
}
return r;
}
int dep[N], fa[N], sz[N], dfn[N], id[N], cnt = 0;
void dfs(int x, int pre, int deep) {
dep[x] = deep;
fa[x] = pre;
sz[x] = 1;
dfn[x] = ++cnt;
id[cnt] = x;
for(int i = head[x]; i; i = Next[i]) {
int y = ver[i];
if(y == pre) continue;
dfs(y, x, deep + 1);
sz[x] += sz[y];
}
}
int top[N];
struct SegmentTree {
int l, r;
int mx, mn;
} tree[N << 2];
void build(int p, int l, int r) {
tree[p].l = l, tree[p].r = r;
if(l == r) {
tree[p].mx = tree[p].mn = t[id[l]];
return;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build((p << 1) | 1, mid + 1, r);
tree[p].mx = max(tree[2 * p].mx, tree[2 * p + 1].mx);
tree[p].mn = min(tree[2 * p].mn, tree[2 * p + 1].mn);
}
int query(int p, int L, int R, int low) {
if(tree[p].l >= L && tree[p].r <= R) {//完全覆盖
if(tree[p].mn >= low) {//这个区间全选
return tree[p].r - tree[p].l + 1;
} else if(tree[p].mx < low) {//这个区间全不选
return 0;
} else {//此时无法直接返回,需要分别再递归
int mid = (tree[p].l + tree[p].r) >> 1;
return query(2 * p, L, R, low) + query(2 * p + 1, L, R, low);
}
}
int mid = (tree[p].l + tree[p].r) >> 1;
int ret = 0;
if(L <= mid) ret += query(2 * p, L, R, low);
if(R > mid) ret += query(2 * p + 1, L, R, low);
return ret;
}
int main() {
cin.tie(0);
ios::sync_with_stdio(false);
cin >> n;
tt = (int)(log(n) / log(2)) + 1;
for(int i = 1; i <= n; i++) head[i] = d[i] = 0;
tot = 0;
for(int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
for(int i = 1; i <= n; i++) {
cin >> t[i];
}
bfs();//树上倍增预处理出f数组
dfs(1, 0, 1);//求dfn的预处理
build(1, 1, n);
int q;
cin >> q;
while(q--) {
int x, l, r;
cin >> x >> l >> r;
if(!(t[x] >= l && t[x] <= r)) { cout << 0 << endl; continue;}
int subrt = gettop(x, r);//树上倍增
//查找以subtr为根的子树有多少点的值大于l
//子树对应区间为:[dfn[subrt], dfn[subrt] + sz[subrt] - 1]
cout << query(1, dfn[subrt], dfn[subrt] + sz[subrt] - 1, l) << endl;
}
return 0;
}