zoukankan      html  css  js  c++  java
  • 最基础权值线段树

    整着整着主席树忽然发现(对于查询静态区间第k大的)前置知识好像没有说...

    回来整权值线段树 ((QoQ))

    权值线段树

    声明:本知识点不需要一般线段树作为前置知识,可以视作与一般线段树分离开来的体系

    然而精通一般线段树可以更加方便地理解权值线段树,而且一般线段树的应用也更广,

    所以建议先学习一下一般线段树,当然不学...好像也可...

    但是还是建议学一学...因为后面的操作跟一般线段树简直如出一辙,这样一来,倒不如先去学了应用更为广泛的一般线段树再来了解,一举两得,未尝不可.

    前置知识:

    • 离散化.

    离散化讲的就是只维护数的大小关系,不维护数值本身,

    就是当我们不关心数值本身时,只记录每个数在(无重复元素)数列中的大小排名

    例如:

    数列(1 100 3)

    将其离散化后只保留排名,成为以下数列:

    (1 3 2)

    解释一下,就是(3)是第二大的数,所以是(2),同理,(100)就是只能"摧眉折腰"当一个(3)

    那么这有什么用呢?

    有些题目需要根据数值大小关系开空间,然而并没有辣么大的空间给你用...

    举个非常简单的例子:

    • 桶排序

    我觉的桶排序都不会就不用看权值线段树了8...

    就是对于值域内的每个元素开一个count记录其出现次数,没有为0,然后就完成了排序...

    好像是O(N)的NB算法...然而空间大到飞起

    这是我们只要维护每个数值在原数列的排名,然后基于此开数组,就好多了对不对?

    那么这个东西这么好...

    并不...其实应该问:

    那么怎么实现呢?

    下面给出比较简单的实现方法:
    开一个vector动态数组,然后...

    fuck我还要整动态数组...

    就像这样:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<vector>
    using namespace std;
    vector<int> u;
    int getid(int x){
    	return lower_bound(u.begin(),u.end(),x)-u.begin()+1;
    }
    int main(){
    	for(i from 1 to n)
    		u.push_back(read(ahaha));
    	sort(u.begin(),u.end());
        u.erase(unique(u.begin(),u.end()),u.end());
    	...
    } 
    

    其中:

    (getid)函数是用来获取元素排名的,

    (lower\_bound)函数是用来查询数组中大于等于x的数中最小的数的地址,做相应运算即可得到其排名,

    (sort)排序不解释,当然如果有需求可以自己重载大于小于号或者自己写compare函数,

    (unique)函数用来去重,用法是传入首尾地址即可,然后它返回的地址是去重之后有效数组的结尾地址的下一个地址

    (头疼)

    就是整理出的没用的数组的第一个地址,

    然后用(unique)函数返回的地址作首,消掉后面的部分,剩下的就是有效数组

    来模拟一下:

    (5 1 48 23 100 5)

    读取存入不解释...

    进行排序( o)

    (1 5 5 23 48 100)

    然后去重( o)

    (1 5 23 48 100 5)

    顺便(erase)一下以真正去除无用数组,

    (1 5 23 48 100)

    对于(getid)操作,就是背过找到当前位置地址与首位置的差值,当然根据经验(植树问题)需要+1.

    这样就方便地完成了离散化...

    因为线段树啥的本身复杂度就是(O(nlogn))的所以关于时间没什么好担心的

    (所以我们可以借助(O(nlogn))的复杂度的(sort)完成(O(n))的排序...)

    然而离散化并不是只有这种垃圾操作,而且这只是一种简单的实现而已,反正比手动模拟好写多了

    前置芝士部分告一段落

    下面是正式的权值线段树,

    权值线段树的作用是什么呢?

    其本身就是一个桶,用来统计并维护值域区间内某个数出现的次数,

    所谓值域区间,就是第(l)小的数到第(r)小的数的区间,

    这里究竟是第(l)(或(r))小还是大根据自己来定,本文主要是维护从小到大的区间的

    说人话就是:维护的(l)小的数到第(r)小的数一共几个

    先不要看这种操作有什么意义

    所以说,对于权值线段树,我们可以不管数列原本的顺序,只看数字出现的次数,并根据其大小排名进行整理

    具体实现原理如下:

    • 也就是说,对于树上的每个节点,最少只需维护一个信息:子树大小,就是当前区间内共有多少元素

    • 其原理就像是桶排序,对于第(i)个叶子节点,即区间长度为1的节点,其维护的是大小排名为i的数出现过多少次,

    • 对于非叶节点,其维护的就是该区间内元素的个数,

      对于非叶节点的合并,就是将区间内元素个数相加,作为这整个区间的元素个数,

      这样一来就完成了对于值域区间内元素个数的维护,

    即:每个节点保存该区间内第(l)大的元素到第(r)大的元素的个数

    那么有了这棵树,我们要实现什么,如何实现呢?

    操作

    因为权值线段树本身就不是什么需要修改的数据结构,所以现在所要求的就是掌握基本的查询操作

    这里唯一的重要操作就是查询第(k)小值,

    或是实现查询第(l)小到第(r)小的区间的元素个数(因为没怎么看过这样用的所以本篇博客并不整理)

    这操作主要就是模拟,掌握思路还可以方便理解平衡树...

    当然还有其他操作,比如最基本的插入操作,就是给第(k)小元素个数加1,其实思路差不多

    (insert)插入操作

    对区间进行二分然后插入,思路类似于线段树的单点修改,

    具体思路就是如果当前区间大于(等于)目标排名,往左找,否则往右找,

    inline void insert(ci k,ci l,ci r,ci v){
    	if(l==r){
    		sum[k]++;
    		return ;
    	}int mid=l+r>>1;
    	if(v<=mid) insert(k<<1,l,mid,v);
    	else insert(k<<1|1,mid+1,r,v);
    	sum[k]=sum[k<<1]+sum[k<<1|1];
    }
    

    (query)查询第(k)大操作

    好像也差不多吧...

    inline int query(ci k,ci l,ci r,ci v){
    	if(l==r) return sum[k];
    	int mid=l+r>>1;
    	if(v<=mid) return query(k<<1,l,mid,v);
    	else return query(k<<1|1,mid+1,r,v);
    }
    

    总代码

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<vector>
    #define ci const int & //speed up!
    using namespace std;
    const int N=100005;
    int n;
    int a[N];
    int sum[N];
    vector<int> u;
    inline int getid(ci v){
    	return lower_bound(u.begin(),u.end(),v)-u.begin()+1;
    }
    inline void insert(ci k,ci l,ci r,ci v){
    	if(l==r){
    		sum[k]++;
    		return ;
    	}int mid=l+r>>1;
    	if(v<=mid) insert(k<<1,l,mid,v);
    	else insert(k<<1|1,mid+1,r,v);
    	sum[k]=sum[k<<1]+sum[k<<1|1];
    }
    inline int query(ci k,ci l,ci r,ci v){
    	if(l==r) return sum[k];
    	int mid=l+r>>1;
    	if(v<=mid) return query(k<<1,l,mid,v);
    	else return query(k<<1|1,mid+1,r,v);
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++){
    		scanf("%d",&a[i]);
    		u.push_back(a[i]);
    	}
    	sort(u.begin(),u.end());
    	u.erase(unique(u.begin(),u.end()),u.end());
    	int m=u.size();
    	for(int i=1;i<=m;i++)
    		insert(1,1,m,getid(a[i]));
    	... //something like luan qi ba zao de query
    	printf("%d",query(1,1,m,getid(a[3]))); //query sum of number "a[3]"
    	return 0;
    }
    

    权值线段树完成

    对于其他操作如前驱,后继,可以考虑自己实现以下,按照思路模拟即可

    然而权值线段树的较广泛应用...

    还是可持久化线段树

  • 相关阅读:
    spring开发_Spring+Hibernate_HibernateDaoSupport
    java开发_STMP邮箱客户端_发送邮件
    struts2开发_userlogin_模拟用户登录
    spring开发_邮箱注册_激活_获取验证码
    MFC笔记(1)
    MFC笔记(2)菜单
    wpf控件开发基础(5) 依赖属性实践
    wpf控件开发基础(3) 属性系统(2)
    wpf控件开发基础(2) 属性系统(1)
    Caliburn笔记依赖注入容器(wpf框架)
  • 原文地址:https://www.cnblogs.com/648-233/p/12084930.html
Copyright © 2011-2022 走看看