HDU 6162 Ch’s gift(树链剖分+线段树)

59 篇文章 0 订阅
12 篇文章 0 订阅

Description

给出一棵 n n 个节点的树,i节点点权为 ci c i m m 次查询,每次查询从s节点到 t t 节点的简单路径上,权值在区间[a,b]之间的点的权值和

Input

多组用例,每组用例首先输入两个整数 n,m n , m 表示点数和查询数,之后输入 n n 个整数ci表示 i i 节点的点权,之后n1行每行输入两个整数 u,v u , v 表示一条树边,最后 m m 行每行输入四个整数s,t,a,b表示一次查询 (1n,m105,1ci109,1ab109) ( 1 ≤ n , m ≤ 10 5 , 1 ≤ c i ≤ 10 9 , 1 ≤ a ≤ b ≤ 10 9 )

Output

对于每次查询,输出结果

Sample Input

5 3
1 2 1 3 2
1 2
2 4
3 1
2 5
4 5 1 3
1 1 1 1
3 5 2 3

Sample Output

7 1 4

Solution

首先树链剖分把一条树上路径转化为若干段连续的区间,问题转化为区间中权值介于 [a,b] [ a , b ] 的数字之和,用线段树维护区间最值和区间和,如果区间最小值不小于 a a 且区间最大值不超过b,说明区间所有数字都介于 [a,b] [ a , b ] ,对答案的贡献就是区间和,如果区间最大值小于 a a 或者区间最小值大于b则该区间对答案没有贡献,否则把区间分成两部分再分别求解,时间复杂度 O(mlog22n) O ( m l o g 2 2 n )

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
typedef long long ll;
#define maxn 100005 
struct Edge
{
    int to,next;
}edge[2*maxn];
int head[maxn],tot;
int idx;//时间戳,用于得到dfs序 
int size[maxn];//size[i]表示以i节点为根节点的子树中节点个数 
int fa[maxn];// fa[i]表示i节点的父亲节点 
int son[maxn];//son[i]表示i节点的重儿子节点(如果没有重儿子则son[i]=i) 
int dep[maxn];//dep[i]表示i节点在树中的深度 
int top[maxn];//top[i]表示i节点所处重链中深度最低的节点 
int id[maxn];//id[i]表示i节点的dfs序 
int pos[maxn];//pos[i]表示dfs序为i的节点 
void init()
{
    idx=tot=0;
    dep[1]=fa[1]=0;//默认1节点为根节点,初始化其父亲节点为0,深度为0 
    memset(son,0,sizeof(son));
    memset(head,-1,sizeof(head));
}
void add(int u,int v)
{
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;
}
void dfs1(int u)//得到size,fa,dep,son
{
    size[u]=1;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v!=fa[u]) 
        {
            fa[v]=u;//v的父亲节点是u 
            dep[v]=dep[u]+1;//儿子的深度等于父亲的深度加一 
            dfs1(v);//深搜 
            size[u]+=size[v];//父亲节点为根节点的子树节点个数等于各儿子节点为根节点的子树节点个数之和加一(父亲节点本身) 
            if(size[son[u]]<size[v]) son[u]=v;//更新重儿子 
        }
    }
}
void dfs2(int u,int topu)//得到top,id,pos,l,r 
{
    top[u]=topu;
    id[u]=++idx;//得到u节点的dfs序 
    pos[idx]=u;//记录这个dfs序对应的节点 
    if(son[u]) dfs2(son[u],top[u]);//有重儿子首先深搜重儿子 
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v!=fa[u]&&v!=son[u]) dfs2(v,v);//深搜所有儿子节点 
    }
}
int n,m,c[maxn];
#define ls (t<<1)
#define rs ((t<<1)|1)
int Max[maxn<<2],Min[maxn<<2];
ll Sum[maxn<<2];
void push_up(int t)
{
    Max[t]=max(Max[ls],Max[rs]);
    Min[t]=min(Min[ls],Min[rs]);
    Sum[t]=Sum[ls]+Sum[rs];
}
void build(int l,int r,int t)
{
    if(l==r)
    {
        Max[t]=Min[t]=Sum[t]=c[pos[l]];
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,ls);build(mid+1,r,rs);
    push_up(t);
}
ll query(int L,int R,int l,int r,int t,int a,int b)
{
    if(L<=l&&r<=R)
    {
        if(Min[t]>=a&&Max[t]<=b)return Sum[t];
        if(Min[t]>b||Max[t]<a||l==r)return 0;
    }
    int mid=(l+r)/2;
    ll ans=0;
    if(L<=mid)ans+=query(L,R,l,mid,ls,a,b);
    if(R>mid)ans+=query(L,R,mid+1,r,rs,a,b);
    return ans;
}
ll Solve(int u,int v,int a,int b)
{
    ll ans=0;
    int top1=top[u],top2=top[v];
    while(top1!=top2)
    {
        if(dep[top1]<dep[top2])
        {
            swap(top1,top2);
            swap(u,v);
        }
        ans+=query(id[top1],id[u],1,n,1,a,b);
        u=fa[top1];
        top1=top[u];
    }
    if(dep[u]>dep[v]) swap(u,v);
    ans+=query(id[u],id[v],1,n,1,a,b);
    return ans;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    { 
        init();
        for(int i=1;i<=n;i++)scanf("%d",&c[i]);
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            add(u,v),add(v,u);
        }
        dfs1(1);
        dfs2(1,1); 
        build(1,n,1);
        for(int i=1;i<=m;i++)
        {
            int l,r,a,b;
            scanf("%d%d%d%d",&l,&r,&a,&b);
            printf("%I64d%c",Solve(l,r,a,b),i==m?'\n':' ');
        }
    }
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值