原题链接
https://ac.nowcoder.com/acm/contest/11260/E
题目大意
火山国家有 n n n个城市,编号从 1 1 1到 n n n,城市 1 1 1是首都,有一座大火山。城市 i i i的温度是 t i t_i ti。
n n n个城市用 n − 1 n-1 n−1条无向边相连,第 i i i条边连接城市 u i u_i ui和 v i v_i vi。如果 u i u_i ui比 v i v_i vi更接近首都,则 t u i > t w i t_{u_i}>t_{w_i} tui>twi。首都的温度是最高的。
如果在城市
x
x
x爆发生存温度为
[
l
,
r
]
[l,r]
[l,r]的病毒,求会感染多少城市。
若一个城市的温度位于
[
l
,
r
]
[l,r]
[l,r]之内,且它与另一个受感染的城市相连,则它也会被感染。
题解
因为 n n n个城市由 n − 1 n-1 n−1条边连接,所以整个地图可以看出以首都(即城市编号为 1 1 1的城市)为根的一棵树,且随着节点的深度越来越大,其城市温度越来越小。
首先,我们先通过
d
f
s
dfs
dfs序将这棵树存进数组里,并记录每个节点的父节点、时间戳,减小代码复杂度。
以下图为例:
其展开的 d f s dfs dfs序为 { 1 , 2 , 3 , 4 , 5 , 6 } \{1,2,3,4,5,6\} {1,2,3,4,5,6}
我们可以通过倍增的方法来寻找某节点的祖先节点中温度≤ r r r的节点,因为这棵树上每条链的值从深到浅严格单调递增,所以若该节点温度在 [ l , r ] [l,r] [l,r]之间,则倍增寻找祖先节点时只需考虑温度是否超过 r r r即可(若向下寻找子节点只需考虑是否低于 l l l)。
找到祖先节点后,我们就可以在以此节点为根的子树中查找温度≥ l l l的城市。若直接遍历,复杂度太大,会TLE。因为我们之前已经把树展开成数组,以上图为例,假设我们需要查找以 2 2 2为根的子树:
则我们需要查找的数值:{1,2,3,4,5,6}
我们只需要开一个线段树维护温度在 [ l , r ] [l,r] [l,r]之间的城市数量的前缀和,我们就可以直接在线计算这个前缀和的值,此时查找的复杂度为 O ( q × l o g n ) O(q\times log_n) O(q×logn)。否则我们需要进行离线计算答案。
参考代码
#include<bits/stdc++.h>
#define FOR(i,n,m) for(int i=n;i<=m;i++)
#define For(i,n,m) for(int i=n;i>=m;i--)
#define pb push_back
using namespace std;
const int N=1e5+5;
int n,cnt=0,q;
int dfn[N],edn[N],ord[N],fa[N],root[N],tot=0,v[N];
int up[N][21];
vector<int> e[N];
struct node{int l,r,val;}a[20000100];
void dfs(int x,int f)
{
dfn[x]=++cnt;
ord[cnt]=x;
fa[x]=f;
FOR(i,0,e[x].size()-1)
{
int son=e[x][i];
if(son==f)continue;
dfs(son,x);
}
edn[x]=cnt;
}
//dfs()将树展开
void modify(int x,int f,int l,int r,int pos)
{
if(l==r)
{
a[x].val=a[f].val+1;
return;
}
int mid=l+r>>1;
if(pos<=mid)
{
a[x].r=a[f].r;
a[x].l=++tot;
modify(a[x].l,a[f].l,l,mid,pos);
}
else
{
a[x].l=a[f].l;
a[x].r=++tot;
modify(a[x].r,a[f].r,mid+1,r,pos);
}
a[x].val=a[a[x].l].val+a[a[x].r].val;
}
//处理前缀和
int query(int x,int l,int r,int ll,int rr)
{
if(x==0)return 0;
if(ll<=l&&r<=rr)return a[x].val;
if(l>rr||r<ll)return 0;
int mid=l+r>>1;
return query(a[x].l,l,mid,ll,rr)+query(a[x].r,mid+1,r,ll,rr);
}
//计算前缀和
int main()
{
scanf("%d",&n);
FOR(i,1,n-1)
{
int x,y;
scanf("%d%d",&x,&y);
e[x].pb(y);
e[y].pb(x);
}
FOR(i,1,n)scanf("%d",&v[i]);
v[0]=1<<30;
dfs(1,0);
FOR(i,1,n)up[i][0]=fa[i];
FOR(j,1,20)
FOR(i,1,n)
up[i][j]=up[up[i][j-1]][j-1];
//up数组记录倍增的祖先节点
FOR(i,1,n)
{
root[i]=++tot;
modify(root[i],root[i-1],1,1e9,v[ord[i]]);
}
scanf("%d",&q);
while(q--)
{
int x,l,r;
scanf("%d%d%d",&x,&l,&r);
if(v[x]<l||v[x]>r){puts("0");continue;}
For(i,20,0)
if(v[up[x][i]]<=r)x=up[x][i];//寻找x的祖先节点中温度小于r的最接近根节点的节点
printf("%d\n",query(root[edn[x]],1,1e9,l,r)-query(root[dfn[x]-1],1,1e9,l,r));
//计算以x为根且温度在[l,r]区间内的城市数量之和(前缀和计算)
}
}