Eyjafjalla--牛客多校第9场--dfs序+线段树+倍增

题目链接: 

Eyjafjalla

题意:

n个城市呈一个树形结构,1号城市是树根,越接近1号城市温度越高,从树根往下,温度满足递减的关系,现有一种生存温度在[L,R]区间病毒在X号城市出现,如果相邻城市的温度在[L,R]区间,则这个城市也会感染病毒。

题目首先给出城市个数n,之后的n-1行是树的边,再之后的一行是n个整数,代表n个城市的温度。
接下来给出m次询问,询问之间是相互独立的,每次询问有三个整数x,l,r,表示在x点爆发一种生存温度在[l,r]的病毒,输出最终感染的城市个数。

思路:

我们知道病毒是沿着树链传播的,沿树链向上,温度递增,沿树链向下,温度递减。也就是说我向上传播传到某个点z时达到最大的t<=R,在往上传播时城市的温度t已经大于R了,这时上面的城市肯定感染不上病毒了,所以在以z为根节点的子树中所有满足温度大于等于L的城市就是最终感染的城市。

如何查找沿树链往上走,最大满足t<=R的城市z呢,我们可以通过倍增法(log_2n)向上爬去查找。

找到z后如何查找以z为根节点的子树中所有满足温度大于等于L的城市个数呢,我们可以通过线段树(或者主席树)查询一段区间内大于某个值的点的个数,所以我们要让z为根节点的子树对应的区间是连续的,通过结点的dfs序建立线段树就可以做到。

样例:

输入

4
1 2
1 3
2 4
10 8 7 6
3
1 10 10
2 6 7
3 7 10

输出

1
0
3

代码:

#include <bits/stdc++.h>

using namespace std;

const int N=1e5+10;
const int M=2*N;
const int inf=1e9+10;
int n,q;
typedef struct Node{
	int l,r,mi,ma;
}Node;
Node tr[N*4];
int w[N],t[N];
int h[N],to[M],ne[M],cnt;
int id[N],R[N],idx[N],dfn,fa[N][21];
void init(){
	memset(h,-1,sizeof(h));
	cnt=0;
	t[0]=inf;
}
void add_edge(int u,int v){
	to[cnt]=v;
	ne[cnt]=h[u];
	h[u]=cnt++;
}
void dfs(int u,int fat){
	id[u]=++dfn;
	idx[dfn]=u;
	w[dfn]=t[u];
	fa[u][0]=fat;
	for(int i=1;i<=20;i++){
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	for(int i=h[u];i!=-1;i=ne[i]){
		int v=to[i];
		if(v!=fat){
			dfs(v,u);
		}
	}
	R[u]=dfn;
}
int query_up(int u,int mat){
	for(int i=20;i>=0;i--){
		if(t[fa[u][i]]<=mat)
			u=fa[u][i];
	}
	return u;
}

void pushup(int u){
	tr[u].ma=max(tr[u<<1].ma,tr[u<<1|1].ma);
	tr[u].mi=min(tr[u<<1].mi,tr[u<<1|1].mi);
}
void build(int u,int l,int r){
	tr[u].l=l,tr[u].r=r;
	if(l==r){
		tr[u].ma=tr[u].mi=w[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
int query(int u,int l,int r,int mit){
//	cout<<"lr:"<<tr[u].l<<" "<<tr[u].r<<" "<<tr[u].mi<<endl;
	if(tr[u].l>=l&&tr[u].r<=r&&tr[u].mi>=mit){
		return tr[u].r-tr[u].l+1;
	}
	if(tr[u].l==tr[u].r)
		return 0;
	int mid=(tr[u].l+tr[u].r)>>1;
	int res=0;
	if(l<=mid&&tr[u<<1].ma>=mit)	
		res+=query(u<<1,l,r,mit);
	if(r>mid&&tr[u<<1|1].ma>=mit)
		res+=query(u<<1|1,l,r,mit);
	return res;
}
int main(){
	init();
	scanf("%d",&n);
	int u,v;
	for(int i=1;i<n;i++){
		scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&t[i]);
	}
	dfs(1,0);
	build(1,1,n);
	
	scanf("%d",&q);
	int x,l,r,z,ans;
	while(q--){
		scanf("%d%d%d",&x,&l,&r);
		if(t[x]>r||t[x]<l){
			printf("0\n");
			continue;
		}
		z=query_up(x,r);
		ans=query(1,id[z],R[z],l);
		printf("%d\n",ans);
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值