2021牛客多校#9 E-Eyjafjalla

原题链接
						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 n1条无向边相连,第 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 n1条边连接,所以整个地图可以看出以首都(即城市编号为 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]区间内的城市数量之和(前缀和计算)
	}
} 
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值