E-Eyjafjalla(2021牛客暑期多校训练营9)【主席树】
思路
以点1为根,可以发现这棵树有这样的性质:深度越深,温度越低。所以我们从当前点往根节点走,一直走到父亲节点的温度不在 [ l , r ] [l, r] [l,r] 区间内时停下,此时所在的节点就是病毒能够扩散到的深度最低的节点(这一步可以用倍增来做)。
以这个节点为根节点的子树包含了所有被感染的节点,也就是说这颗子树外的节点不可能被感染。被感染的条件是温度在区间 [ l , r ] [l, r] [l,r] 之间,所以此时问题就转化为了找出这颗子树下有多少温度大于等于 l l l 的节点,而主席树的基本操作就是统计某一权值区间内有多少节点,由此我们可以结合dfs序(或者树剖)将树上操作转化为区间操作,于是用倍增+主席树(dfs序建树)就能AC辽~
代码
比赛的时候觉得这用树剖做不是易如反掌,然后赛后写了一天写不出来,最后还是和队友一个方法做了qwq(队友yyds
#include <bits/stdc++.h>
using namespace std;
const int N = 4e5 + 10, M = 1e5 + 10;
int n, q;
int a[N], tot, dfstot;
vector<int> nums;
int head[N], es[N * 2], nxt[N], dfsl[N], dfsr[N], par[N][30];
struct query //询问
{
int x, l, r;
}qs[N];
struct Node
{
int l, r;
int cnt;
} tr[N * 4 + M * 35];
int root[N], idx;
//离散化后找节点位置 -- l = find(l)
int find(int x)
{
return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}
//建树 -- root[0] = build(0, num.size() - 1)
int build(int l, int r)
{
int p = ++ idx;
if (l == r)
return p;
int mid = l + r >> 1;
tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);
return p;
}
//修改 -- root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[x]));
// i:当前是第i个版本 x:要修改的值的位置
int insert(int p, int l, int r, int x)
{
int q = ++ idx;
tr[q] = tr[p];
if (l == r)
{
tr[q].cnt ++ ;
return q;
}
int mid = l + r >> 1;
if (x <= mid)
tr[q].l = insert(tr[p].l, l, mid, x);
else
tr[q].r = insert(tr[p].r, mid + 1, r, x);
tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
return q;
}
//查询x到y区间中范围在[s, t]之间的节点个数
//调用 query(root[x], root[y], s, t, 0, nums.size() - 1) 获得个数
int query(int x, int y, int s, int t, int l, int r)
{
if (s <= l && r <= t)
{
return tr[y].cnt - tr[x].cnt;
}
int mid = (l + r) >> 1;
int res = 0;
if (s <= mid)
{
res += query(tr[x].l, tr[y].l, s, t, l, mid);
}
if (mid < t)
{
res += query(tr[x].r, tr[y].r, s, t, mid + 1, r);
}
return res;
}
//查询p到q区间中的第k大数
//调用 query(root[r], root[l - 1], 0, nums.size() - 1, k) 获得其数组位置
int query(int q, int p, int l, int r, int k)
{
if (l == r) return r;
int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
int mid = l + r >> 1;
if (k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k);
else return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);
}
void addEdge(int u, int v)
{
es[++idx] = v;
nxt[idx] = head[u];
head[u] = idx;
}
//边dfs边建树
void dfs(int u, int fa)
{
dfsl[u] = ++dfstot; //dfsl维护了这颗子树dfs序的最小值
root[tot + 1] = insert(root[tot], 0, nums.size() - 1, find(a[u])); //建树
tot++; //第tot个版本
par[u][0] = fa;
for(int i = 1; i < 20; i++)
par[u][i] = par[par[u][i - 1]][i - 1];//倍增
for(int i = head[u]; ~i; i = nxt[i])
{
int v = es[i];
if(v == fa)
continue;
dfs(v, u);
}
dfsr[u] = dfstot; //dfsr维护了这颗子树dfs序的最大值
//得到dfsl与dfsr后,这颗子树就变成了一个区间,也就是[dfsl[u], dfsr[u]]这个区间
}
void init()
{
memset(head, -1, sizeof head);
}
int cnt;
int main()
{
init();
scanf("%d", &n);
for(int i = 0, u, v; i < n - 1; i++)
{
scanf("%d%d", &u, &v);
addEdge(u, v);
addEdge(v, u);
}
for (int i = 1; i <= n; i ++ )
{
scanf("%d", &a[i]);
nums.push_back(a[i]);
}
scanf("%d", &q);
for(int i = 1; i <= q; i++)
{
int x, l, r;
scanf("%d%d%d", &x, &l, &r);
qs[i] = {x, l, r};
//将l, r也加入nums(因为是权值线段树)
nums.push_back(l);
nums.push_back(r);
}
//离散化处理
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
root[0] = build(0, nums.size() - 1); //初始版本
dfs(1, -1); //dfs并建树
for(int i = 1; i <= q; i++)
{
int x = qs[i].x;
int l = qs[i].l;
int r = qs[i].r;
if(a[x] < l || a[x] > r) //特判,如果第一个节点就不符合直接退出
{
printf("0\n");
continue;
}
for(int i = 19; i >= 0; i--)
if(par[x][i] > 0 && a[par[x][i]] <= r)
x = par[x][i]; //从当前点往根节点走,一直走到父亲节点的温度不在[l, r]区间内时停下
l = find(l); //找离散化数组中的对应位置
r = find(r);
printf("%d\n", query(root[dfsl[x] - 1], root[dfsr[x]], l, r, 0, nums.size() - 1)); //r的版本减去l - 1的版本,也就是得到了值在l到r之间的数的个数
}
return 0;
}