线段树合并

线段树合并

前言:

不知道为什么最近做题总是能做到线段树合并的题目

前置知识:

权值线段树、线段树动态开点

做法

假设我们要把 Y Y Y树合并到X树上,对于 X X X树上的每一个点

  1. 如果这个点有儿子, Y Y Y树上对应的点有,继续便利;
  2. 如果这个点有儿子, Y Y Y树上对应的点没有,直接跳过;
  3. 如果这个点没有儿子, Y Y Y树上对应的点有,因为我们是动态开点,所以可以直接把 X X X树上这个点的儿子设为Y树上对应点儿子的编号
  4. 两棵树都没有就不管了
    听tjm大佬说线段树合并最好开1e7或5e6?

例题:

P3605 [USACO17JAN]Promotion Counting P

线段树合并版题 但是要先离散化一下我好像还不会,做一棵权值线段树,然后从叶子节点到根节点做一遍 d f s dfs dfs

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int fa[N] , cnt , hd[N] , a[N] , ans[N] , n , ps , num[N];
struct E {
    int to,nt;
} e[N];
struct Tr {
    int l,r,v;
} tr[1900006];
struct R {
    int id,val;
} re[N];
void add(int x,int y) {
    e[++cnt].to=y;
    e[cnt].nt=hd[x];
    hd[x]=cnt;
}
void updata(int x) {
    tr[x].v = tr[tr[x].l].v + tr[tr[x].r].v;
}
void build(int p,int l,int r,int val) {
    if(l == r) {
        tr[p].v = 1;
    }
    else {
        int mid=l + r >> 1;
        if(val <= mid) {
            if(!tr[p].l) {
                tr[p].l = ++ps;
            }
            build(tr[p].l,l,mid,val);
        }
        else {
            if(!tr[p].r) {
                tr[p].r = ++ps;
            }
            build(tr[p].r,mid+1,r,val);
        }
        tr[p].v = 1;
        updata(p);
    }
}
void Marge(int x,int y,int l,int r) {
    if(l == r) {
        tr[x].v+=tr[y].v;
    }
    else {
        int mid=l + r >> 1;
        if(!tr[x].l && tr[y].l) {
            tr[x].l = tr[y].l;
        }
        else if(tr[x].l && tr[y].l) {
            Marge(tr[x].l,tr[y].l,l,mid);
        }
        if(!tr[x].r && tr[y].r) {
            tr[x].r = tr[y].r;
        }
        else if(tr[x].r && tr[y].r) {
            Marge(tr[x].r,tr[y].r,mid+1,r);
        }
        updata(x);
    }
}
int query(int p,int l,int r,int val) {
    if(l == r) {
        return 0;
    }
    else {
        int mid=l+r>>1 , sum=0;
        if(val <= mid) {
            if(tr[p].l)
                sum += query(tr[p].l,l,mid,val) + tr[tr[p].r].v;
        }
        if(val > mid) {
            if(tr[p].r) 
                sum += query(tr[p].r,mid+1,r,val);
        }
        return sum;
    }
}
void fans(int x) {
    int y;
    for(int i=hd[x];i;i=e[i].nt) {
        y=e[i].to;
        fans(y);
        Marge(x,y,1,n);
    }
    ans[x] = query(x,1,n,a[x]);
}
int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        scanf("%d",&a[i]);
        num[i]=a[i];
    }
    sort(num+1,num+n+1);
    int t=unique(num+1,num+n+1)-num-1;
    for(int i=1;i<=n;i++) {
        a[i]=lower_bound(num+1,num+t+1,a[i])-num;
    }
    ps=t;
    for(int i=1;i<=n;i++) {
        build(i,1,n,a[i]);
    }
    for(int i=2;i<=n;i++) {
        scanf("%d",&fa[i]);
        add(fa[i],i);
    }
    fans(1);
    for(int i=1;i<=n;i++) {
        printf("%d\n",ans[i]);
    }
    return 0;
}

[HNOI2012]永无乡

雨天的尾巴

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值