线段树进阶之权值树

抱怨

真不应该直接去刚(主席树:可持久化线段树),从中午12点学晚上到快7点,才理解,这不是我能刚的动的,为什么?因为学主席树之前要先学权值线段树,如果不了解权值线段树,直接刚主席树,根本不知道在说个啥玩意,效率很低

现在我讲一下权值线段树:

给一串数 1,2,3,1,5,4 很明显这串数的值域是[1,5],我们把值域二分为区间,求每一个区间有多少数,然后可以以通过这种方案来求:第k大或者第k小的数

第一步:

直接建好一个空树
void Set(int k,int l,int r)//建一个空树
{
    val_tree[k].l=l;
    val_tree[k].r=r;
    val_tree[k].sum=0;
    if(l==r)
    {
        return;
    }
    int mid=(l+r)>>1;
    Set(k<<1,l,mid);
    Set(k<<1|1,mid+1,r);
}

第二步:

更新区间中值的个数
void update(int k,int pos,int w)//更新
{
    if(val_tree[k].l==val_tree[k].r)
    {
        val_tree[k].sum+=w;
        return ;
    }
    int mid=(val_tree[k].l+val_tree[k].r)>>1;
    if(mid>=pos)
    {
        update(k<<1,pos,w);
    }
    else
    {
        update(k<<1|1,pos,w);
    }
    push_up(k);
}

第四步

查询不同区间个数的数量
int query_num(int k,int l,int r)
{
    if(val_tree[k].l>=l&&val_tree[k].r<=r)
    {
        return val_tree[k].sum;
    }
    int mid=(val_tree[k].l+val_tree[k].r)>>1;
    if(mid>=r)
    {
        return query_num(k<<1,l,r);
    }
    else if(mid<l)
    {
        return query_num(k<<1|1,l,r);
    }
    return query_num(k<<1,l,mid)+query_num(k<<1|1,mid+1,r);

}

权值线段树+二分离散化: 完整求不同值域区间含有数的数量

#include<bits/stdc++.h>
using namespace std;
//权值树
#define maxn 1005
struct node
{
    int l,r,sum;
} val_tree[maxn*40];
int a[maxn];
//离散化
vector<int>vec;
int getid(int x)
{
    return lower_bound(vec.begin(),vec.end(),x)-vec.begin()+1;
}
void push_up(int k)//维护区间
{
    val_tree[k].sum=val_tree[k<<1].sum+val_tree[k<<1|1].sum;
}
void Set(int k,int l,int r)//建一个空树
{
    val_tree[k].l=l;
    val_tree[k].r=r;
    val_tree[k].sum=0;
    if(l==r)
    {
        return;
    }
    int mid=(l+r)>>1;
    Set(k<<1,l,mid);
    Set(k<<1|1,mid+1,r);
}
void update(int k,int pos,int w)//更新
{
    if(val_tree[k].l==val_tree[k].r)
    {
        val_tree[k].sum+=w;
        return ;
    }
    int mid=(val_tree[k].l+val_tree[k].r)>>1;
    if(mid>=pos)
    {
        update(k<<1,pos,w);
    }
    else
    {
        update(k<<1|1,pos,w);
    }
    push_up(k);
}
int query_num(int k,int l,int r)
{
    if(val_tree[k].l>=l&&val_tree[k].r<=r)
    {
        return val_tree[k].sum;
    }
    int mid=(val_tree[k].l+val_tree[k].r)>>1;
    if(mid>=r)
    {
        return query_num(k<<1,l,r);
    }
    else if(mid<l)
    {
        return query_num(k<<1|1,l,r);
    }
    return query_num(k<<1,l,mid)+query_num(k<<1|1,mid+1,r);

}
int main()
{
    int n,x;
    scanf("%d",&n);

    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
        vec.push_back(a[i]);
    }
    sort(vec.begin(),vec.end());
    vec.erase(unique(vec.begin(),vec.end()),vec.end());//离散化
    //printf("%d\n",vec.size());
    Set(1,1,vec.size());//值域1---n
    for(int i=1; i<=n; i++)
    {
        update(1,getid(a[i]),1);
    }
    int l,r;
    while(1)
    {
        cin>>l>>r;
        cout<<query_num(1,l,r);
    }
}

上面可能讲的不太细致,现在我们模拟一下过程来进行权值线段树讲解:

举一个栗子:假如我们要求 1 2 2 3 4 6 整个区间的第k大,可以进行修改某个点。
这里说一下,为什么不用sort() 假如我们可以询问 n次,每次询问分为查询和更改
如果用sort()写最坏情况时间复杂度 O(n*nlogn),每次修改一次都需要排序,所以如果修改n-1次查询1次。呢么时间复杂度==O(n*nlogn),而权值线段树查第k大的,只有O(n*logn),因为每次查询和更改的复杂度都是O(logn)
理解线段树之后,权值线段树其实就是把建树区间变为值域了,如果值域太大,而且数据量不是很大,呢么就离散化处理一下就ok了
离散化一下,当前的值域为1--n
建一个树,记录所有区间有多少个数,然后根据左右区间的大小和数量关系求取第k大即可

这是求取区间最大和最小,用权值线段树写的:

#include<cstdio>
#include<algorithm>
using namespace std;
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
//求第k大
const int maxn=1e4+5;
int a[maxn];
vector<int>vec;
int getid(int x)//离散化
{
    return lower_bound(vec.begin(),vec.end(),x)-vec.begin()+1;
}
struct node
{
    int l,r,sum;
} tree[maxn*4];
void pushup(int k)
{
    tree[k].sum=tree[k<<1].sum+tree[k<<1|1].sum;
}
void build(int k,int l,int r)
{
    tree[k].l=l;
    tree[k].r=r;
    tree[k].sum=0;
    if(l==r)
    {
        return ;
    }
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
}
void update(int k,int pos,int w)
{
    if(tree[k].l==tree[k].r)
    {
        tree[k].sum+=w;
        return ;
    }
    int mid=(tree[k].l+tree[k].r)>>1;
    if(mid>=pos)
        update(k<<1,pos,w);
    else
        update(k<<1|1,pos,w);
    pushup(k);
}
int query_kth(int k,int ik)
{
    if(tree[k].l==tree[k].r)
    {
        return tree[k].l;
    }
    if(tree[k<<1].sum>=ik)
        return query_kth(k<<1,ik);
    else
        return query_kth(k<<1|1,ik-tree[k<<1].sum);
}
int main()
{

    int n=5;
    //scanf("%d",&n);
    for(int i=1;i<=5;i++)
    {
        scanf("%d",&a[i]);
        vec.push_back(a[i]);
    }
    sort(vec.begin(),vec.end());
    vec.erase(unique(vec.begin(),vec.end()),vec.end());
    build(1,1,vec.size());
    for(int i=1;i<=n;i++)
    {
        update(1,getid(a[i]),1);
    }

    int ans2=vec[query_kth(1,n)-1];
    int ans1=vec[query_kth(1,1)-1];
    printf("%d %d\n",ans1,ans2);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值