2021牛客暑期多校训练营9 E.Eyjafjalla 倍增+线段树合并

题意:
给出一棵树 父亲节点的权值>儿子节点
1e5组询问 与x号点相连的点[L,R]范围内的点有多少个

对于每个节点来说他所有的儿子都比自己小
那么只需要看这个子树中的L-R的数量 然后接下来是对于这个节点的父亲来说 他需要找到最后一个小于等于R的父亲节点 那么最终答案就是找到的父亲节点的子树中 L-R权值的数量
找父亲我们可以通过倍增来找
统计子树信息 我的写法是一棵暴力的线段树合并 对于线段树合并来说 他是离线算法 高效统计每棵子树的信息
复杂度分析:最高是一颗完美二叉树 从根节点往下分析
每次合并复杂度 s z [ u ] / 2 ∗ ( log ⁡ ( n ) ) sz[u]/2*(\log(n)) sz[u]/2(log(n))
接下来复杂度其实就和cdq分治很像了

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef int LL;
const int N = 1e6+10,INF=1e9,M=2e5+10;
struct Edge{
    int v,next;
}edge[M<<1];
int head[M],tot;
void add(int u,int v){
    edge[++tot]={v,head[u]};
    head[u]=tot;
}
int a[M];
int fa[M][22];
int root[M];
struct Node{
	int l,r;
	int sum;
}tr[N<<2];
#define ls tr[u].l
#define rs tr[u].r
int idx,n;
int A[M],An;
int change(int u,LL l,LL r,LL k,LL val){
	if(!u){
		u=++idx;
	}
	if(l==r){
		tr[u].sum=tr[u].sum+val;
		return u;
	}	
	LL mid=l+r>>1;
	if(k<=mid)ls=change(ls,l,mid,k,val);
	else rs=change(rs,mid+1,r,k,val);
    tr[u].sum=tr[ls].sum+tr[rs].sum;
	return u;
}
int merge(int u,int v,LL l,LL r){
    if(!u||!v)return u|v;
    tr[++idx]=tr[u];
    u=idx;
    if(l==r){
        tr[u].sum+=tr[v].sum;
        return u;
    }
	LL mid=l+r>>1;
	ls=merge(ls,tr[v].l,l,mid);
	rs=merge(rs,tr[v].r,mid+1,r);
    tr[u].sum=tr[ls].sum+tr[rs].sum;
	return u;
} 
LL query(int u,LL l,LL r,LL a,LL b){
	if(!u)return 0;
	if(a<=l&&r<=b){
		return tr[u].sum;	
	}
	LL mid=l+r>>1;
	int res=0;
	if(a<=mid)res+= query(ls,l,mid,a,b);
	if(b>mid) res+= query(rs,mid+1,r,a,b);
    tr[u].sum=tr[ls].sum+tr[rs].sum;
	return res;
}
void dfs(int u,int father){
    for(int i=head[u];i;i=edge[i].next){
    	int v=edge[i].v;
    	if(v==father)continue;
    	fa[v][0]=u;
    	for(int j=1;j<=20;j++){
    		fa[v][j]=fa[fa[v][j-1]][j-1];
		}
    	dfs(v,u);
    	root[u]=merge(root[u],root[v],1,An);
	}
	root[u]=change(root[u],1,An,a[u],1);
}
int get(int u,int r){
    for(int k=20;k>=0;k--){
        if(fa[u][k]&&a[fa[u][k]]<=r&&fa[u][k]){
        	u=fa[u][k];
		}
    }
    return u;
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        A[++An]=a[i];
    }
    A[++An]=0;
    A[++An]=1e9;
    sort(A+1,A+1+An);
    An=unique(A+1,A+1+An)-(A+1);
    for(int i=1;i<=n;i++){
    	a[i]=lower_bound(A+1,A+1+An,a[i])-A;
	}
    dfs(1,1);
    int m;
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
    	int x,l,r;
    	scanf("%d%d%d",&x,&l,&r);
    	l=lower_bound(A+1,A+1+An,l)-A;
    	r=upper_bound(A+1,A+1+An,r)-A;
    	r--;
    	if(a[x]<l||a[x]>r){
    		puts("0");
    		continue;
		}
		int t=get(x,r);
		printf("%d\n",query(root[t],1,An,l,r));
	}
    return 0;
}
/*
5
1 2
2 4
1 3
3 5
9 7 4 5 1
9
5 1 9
*/
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值