【权值线段树】离散化介绍 (+利用 线段树 求逆序对)

先介绍一下离散化
桶排大家应该知道,就是开一个数组(下标为数值,记录了该数值的出现次数)然后遍历过去如果出现次数不为零,那就输出这些数字,理论时间复杂度可以达到O(N)但是由于内存限制,不能开很大的数组。

然而 如果某个数列中的数字不要求大小确定,只要求这些数字有相对的大小就够了的话,离散化就有了用武之地

举个例子:数列

3 8 7 5 2000000000000000

我们发现有几个数之间差距很大,但是我们用不到数值的大小,只要求相对大小,那怎么办呢?
观察下面的数列:

1 4 3 2 5

真巧,这个数列和上面的数列各个数字之间的相对大小是一样的并且,让很大的数据缩小了,这样离散化了之后就可以处理一些原本处理不了的问题

离散化比较OK的复杂度是O(NlogN)
STL大法好,代码见下:

    for(int i=1;i<=n;++i)
        a[i]=read(),b[i]=a[i];//读入数组a,b数组记录下,等会儿用(read是读入优化)
    sort(b+1,b+n+1);//排序b数组,避免a数组顺序打乱
    int len=unique(b+1,b+n+1)-b-1;//unique:STL自带函数,去重并返回去重后数组的长度+1(所以这里后面要-1)
    //(原理上来讲不是这样,但这样理解方便)
    for(int i=1;i<=n;++i){
        int pos=lower_bound(b+1,b+n+1,a[i])-b;//STL自带函数,返回b(有序数组)中第一个大于等于a[i]的位置
        a[i]=pos;//改变这个值为找到的位置
    }

介绍一种神奇的算法:

权值线段树

顾名思义,该线段树中存储的并不是普通线段树记录的线段端点,而是 类似一个桶一样的东西

最简单的例子:求逆序对
该题可以用树状数组做,可以用归并排序思想做,可以用splay做......
不过,既然可以用树状数组用,也可以用线段树做

【思路】

先建一棵很大的线段树,然后用权值为下标建树,每一次读到一个数,就找比这个数大的个数,找到了之后加到ans里,接着更新线段树(这个数出现的次数+1)
PS:这题要加离散化,否则……你懂的

#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#define lson i << 1
#define rson i << 1 | 1
#define M 40005
using namespace std;

inline int read(){
    char chr=getchar();
    int f=1,ans=0;
    while(!isdigit(chr)) {if(chr=='-') f=-1;chr=getchar();}
    while(isdigit(chr))  {ans=ans*10;ans+=chr-'0';chr=getchar();}
    return ans*f;

}
int n;
struct Node{
    int l,r,v;
}t[M<<2];
int a[M],b[M];
void kai(){
    freopen("test1.txt","r",stdin);
}

void push_up(int i){
    t[i].v=t[lson].v+t[rson].v;
}//向上更新

void build(int i,int l,int r){//建树
    t[i].l=l;   t[i].r=r;   t[i].v=0;
    if(l==r){
        return;
    }
    int mid=t[i].l+t[i].r>>1;
    build(i<<1,l,mid);
    build(i<<1|1,mid+1,r);
}

void updata(int i,int x){
    if(t[i].l==t[i].r){
        ++t[i].v;//这个数字出现的次数+1
        return;
    }
    int mid=t[i].l+t[i].r>>1;
    if(x<=mid) updata(lson,x);
    else       updata(rson,x);
    push_up(i);
}//更新节点

int query(int i,int l,int r){
    if(l<=t[i].l&&t[i].r<=r) return t[i].v;
    int mid=t[i].l+t[i].r>>1;
    int x=0;
    if(l<=mid) x+=query(lson,l,r);
    if(mid<r)  x+=query(rson,l,r);
    return x;
}//询问

int main(){
//  kai();
    n=read();
    int ans=0;
    build(1,1,M);
    for(int i=1;i<=n;++i)
        a[i]=read(),b[i]=a[i];
    sort(b+1,b+n+1);
    int len=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;++i){
        int pos=lower_bound(b+1,b+n+1,a[i])-b;
        a[i]=pos;
    } //离散化
    for(int i=1;i<=n;++i){
        int x=a[i];
        ans+=query(1,x+1,M);//找比这个数大的数的出现的总次数
        updata(1,x);//这个数出现次数+1
    }
    printf("%d",ans);
    return 0;
}

打完收工

hia~hia~hia~

原文地址:https://www.cnblogs.com/zhenglw/p/9507884.html

时间: 2024-08-25 06:29:11

【权值线段树】离散化介绍 (+利用 线段树 求逆序对)的相关文章

归并排序,树状数组 两种方法求逆序对

我们知道,求逆序对最典型的方法就是树状数组,可是另一种方法就是Merge_sort(),即归并排序. 实际上归并排序的交换次数就是这个数组的逆序对个数,为什么呢? 我们能够这样考虑: 归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来. 在合并的过程中(设l<=i<=mid,mid+1<=j<=h).当a[i]<=a[j]时.并不产生逆序数:当a[i]>a[j]时.在 前半部分中比a[i]大的数都比a[j]

权值线段树求逆序对问题

我们都知道,求逆序对数量可以用归并排序解决.但是用归并排序只能解决静态的序列问题,没有扩展的区间.因此就有了用权值线段树求逆序对的方法. 1 #include<iostream> 2 #include<iomanip> 3 #include<ctime> 4 #include<climits> 5 #include<algorithm> 6 #include<queue> 7 #include<vector> 8 #inc

Codevs 1688 求逆序对(权值线段树)

1688 求逆序对 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 查看运行结果 题目描述 Description 给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目 数据范围:N<=105.Ai<=105.时间限制为1s. 输入描述 Input Description 第一行为n,表示序列长度,接下来的n行,第i+1行表示序列中的第i个数. 输出描述 Output Description 所

(c) hdu1394* (求逆序对数)(线段树)

(c) hdu1394 如在阅读本文时遇到不懂的部分,请在评论区询问,或跳转 线段树总介绍 线段树求逆序对数比较少见啊(归并排序多快啊...但是本文是讲解线段树写法...),何况这题还加了点别的玩意儿... 1. 本来这种题目要离散化的,可是体中保证了数列0~n-1. 2. 每次把首位放到最末,显然不能 每次都求逆序对 ,于是又到了推 倒 导时间. 由 a1 a2 ... an 变为 a2 a3 ... an a1 减少的逆序对数为 a2~an中比a1小的数的个数 增加的逆序对数为 a2~a1中

BNU 2418 Ultra-QuickSort (线段树求逆序对)

题目链接:http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=2418 解题报告:就是给你n个数,然后让你求这个数列的逆序对是多少?题目中n的范围是n < 500000,所以,暴力是不行的.还是第一次学会用线段树求逆序数,这种方法的时间复杂度是n * log n,是不是很快呢,利用了线段树查询速度快的优势.具体的方法如下: 这里先说一下,如果输入的n个数不是连续的,也就是说把这n个数按从小到大的顺序排列起来不是连续的话,还要先离散化一下,其实也就是把

hdoj 1394 Minimum Inversion Number【线段树求逆序对】

求逆序对有很多算法,这里说一下线段树求逆序对的思想. 知识点:线段树,逆序对,单点更新,成段求和 算法:线段树求逆序数的前提条件是要离散化,变成连续的点,首先建树,每个节点设置一个num值为0. 然后根据逆序对的定义,前面出现过的比当前数大的个数的和,我们需要求前面的比他大的数,其实就相当于从当前a[i]点对他后面所有出现过的数求和一次.然后把当前点的值在线段树叶子节点变为1,表示出现过,并向上更新到线段树里面.比如说样例4 2 1 5 3 首先4后面没有值,4更新为1,4--5区间更新为1,1

求逆序对(线段树版)

一个序列a1,a2,a3...aN,求出满足:ai > aj 且 i < j 的个数. 一个最容易想到的方法就是枚举所有的i,j看看是否满足,显然是O(n^2)的复杂度.不够好. 可以这样考虑,开一个数组保存这n个数出现的位置和对应的次数,这个数组要开到a数组里最大的那个数MAX,也就是hash,初始状态数组里没有元素,每个数对应的个数都是0. 如果考虑第i个数,找到比它大的所有的数 的个数,查找的范围即 ai+1~MAX,这就是到i这个位置的逆序对的总和,接着把a[i]这个数添加到数组里,也

线段树求逆序对

嘘!这里是逆序对的题目链接 以前一直不知道线段树有求逆序对的功能 之前老师提了一下又刚好没听 今天自己模拟了一遍似乎是对了 代码虽短但耗费的空间却大 而归并排序代码虽然复杂却只耗费少量的空间 大概思想: 1.建一棵和最大数值一样大的线段树 2.每次在树中查找这个点的位置 3.在查找的过程中有两种选择 (1).往左子树下去 这时需要将当前节点的值减去左节点的值 (2).往右子树下去 不用操作 4.进入子树,直到遇到这个值所在的区间(l=r=a)的时候 5.将这个区间的值加1,如何维护树 1 #in

HDU 1394 Minimum Inversion Number(线段树求逆序对)

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1394 解题报告:给出一个序列,求出这个序列的逆序数,然后依次将第一个数移动到最后一位,求在这个过程中,逆序数最小的序列的逆序数是多少? 这题有一个好处是输入的序列保证是0 到 n-1,所以不许要离散化,还有一个好处就是在计算在这个序列中比每个数大和小的数一共有多少个的时候可以在O(1)时间计算出来,一开始我没有意识到,还傻傻的用了两层for循环来每次都计算,当然这样果断TLE了.把一个数从第一个移