树状数组浅显理解以及Ultra-QuickSort的AC代码

本文介绍了一种高效的数据结构——树状数组,它可以在接近O(1)的时间复杂度内完成数组元素的修改与区间求和操作。文章通过对比简单的前缀和数组,详细解释了树状数组的工作原理,并给出了具体的实现代码。此外,还介绍了如何利用树状数组解决Ultra-QuickSort问题的方法。
摘要由CSDN通过智能技术生成
  1. 快速求和
    如何快速地求区间[x,y]的数组和呢?相信很容易知道,只需要建立一个前缀和数组(Tree[i]的数值为import[1]~import[i]的和),就能在O(1)内求出任意区间的和,若只是求任意区间的和,显然这种方法是最快而且在线的。
  2. 树状数组的意义
    一个简单的前缀和数组虽然能在通常意义下做到最快的求和,对于数组的维护却相当费力。对输入数组的单点修改,简单前缀和数组的维护成本为O(n),那么对于需要频繁更改数据的维护,简单前缀数组是不合适的。
    有没有一种方法在修改和求和统计上能够同时做到O(1)呢?很遗憾并没有在所有情况下都这么快的结构。
    退而求其次,我们找到了一种在修改和求和统计速度都及其接近O(1)的结构,那就是树状数组。
  3. 树状数组的由来
    想像这样一种树,它是一种修改版的前缀和,这种微妙的修改使得Tree[i]是i之前多个数的和(但不是严格的从1到i的和),这样使得修改的时候没必要修改像简单前缀和一样的次数。但这似乎使得区间求和不太方便,再想象这种性质:Tree[i]是i前k个数的和而这个k是不确定的,它有时比较小,有时候非常大,这使得不论求小区间还是大区间都能很方便。
    那这个有时大有时小的数如何控制呢?这就是树状数组的核心:lowbit(x)=x&-x,这是一个与x相关的数,能够恰如其分地控制前缀范围。
  4. lowbit(x)函数
    记n为x能够整除2的次数,那么这个函数的值就是2的n次方。
    不难想象,当x为2的k次方时,Tree[lowbit(x)]恰好为1~N所有数的和。当x为奇数时,Tree[lowbit(x)]仅仅是import[x],而x为普通偶数时,lowbit[x]时大时小。
  5. 代码核心:
    参见这篇博客

代码 示例

输入:
第一行一个数N,代表输入的数有N个。
第二行N个正整数
以后K行:
左区间值与右区间值,以空格隔开。回车结束该行输入。
结束输入:
换行,CTRL+Z

#include <iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cstdlib>
using namespace std;

#define lowbit(x) x&-x

int N;

void UPDATE(int*Tree,int k,int x)//更改某点数值
{
    for(int i=k;i<=N;)
    {
        Tree[i]+=x;
        i+=lowbit(i);
    }
}

int ALL(int*Tree,int k)//1~N求和
{
    int Num=0;
    for(int i=k;i>=1;)
    {
        Num+=Tree[i];
        i-=lowbit(i);
    }
    return Num;
}

int main()
{
    scanf("%d",&N);
    int*Tree=(int*)malloc(sizeof(int)*(N+2));
    fill(Tree,Tree+(N+2),0);
    int key;
    for(int i=1;i<=N;i++)
        {
            scanf("%d",&key);
            UPDATE(Tree,i,key);
        }
    int x,y;
    while(scanf("%d%d",&x,&y)!=EOF)
        printf("%d\n",ALL(Tree,y)-ALL(Tree,x-1));
    return 0;
}

如何解决Ultra-QuickSort 的数据范围不适用

理论上,如果没有数据限制,用树状数组解决Ultra-QuickSort无疑是非常好的办法,但我们遇到了一个拦路虎,因为0 ≤ a[i] ≤ 999,999,999,这样大的数据范围是无法开出这么长的数组的,就算能够实现,在实际工程问题中,这样的办法也及其耗费资源,并不是一种经济的办法。
就仅这道题而言,仅仅因为数据范围就用不了树状数组实在是有点可惜。虽然我们无法开出10的9次方的数组,但由于数字的总量很小,如果我们在不改变大小关系的前提下将各个数字“缩小”,直到能够运用树状数组来解决就可以啦。缩小到什么程度最好呢?当然是排序后的每对相邻的数字只相差1,就能最大程度的节约内存啦。
当我们把输入的数据排序好以后,将他们的值都依顺序改为1,2,3,4,5,。。。然后再逆排序,即变成输入顺序就可以啦。这个操作我是用一个结构体和一个额外数组完成的。具体可以看代码。

Ultra-QuickSort 的AC代码

题目百度即可

#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>

using namespace std;

#define lowbit(x) x&-x
int N;

struct Doc
{
    long long num;
    int doc;
};

bool cmp(Doc a,Doc b)
{
    return a.num<b.num;
}

void UPDATE(int*Tree,int k,int x)
{
    for(int i=k;i<=N;)
    {
        Tree[i]+=x;
        i+=lowbit(i);
    }
}

int SUM(int*Tree,int k)
{
    int sum=0;
    for(int i=k;i>=1;)
    {
        sum+=Tree[i];
        i-=lowbit(i);
    }
    return sum;
}

void RESET(Doc*ToTran,int*Array)
{
    sort(ToTran+1,ToTran+N+1,cmp);
    for(int i=1;i<=N;i++)
    {
        Array[ToTran[i].doc]=i;
    }
}

long long BDans(int*Array,int*Tree)
{
    long long ans=0;
    for(int i=1;i<=N;i++)
    {
        //printf("现在插入%d\n",Array[i]);
        UPDATE(Tree,Array[i],1);
        //printf("%d当前SUM是%d\n",i,SUM(Tree,i));
        ans+=i-SUM(Tree,Array[i]);
        //printf("目前积累%d\n",ans);
    }
    return ans;
}

int main()
{
    while(scanf("%d",&N)!=EOF && N)
    {
        Doc*ToTran=(Doc*)malloc(sizeof(Doc)*(N+2));
        int*Array=(int*)malloc(sizeof(int)*(N+2));
        int*Tree=(int*)malloc(sizeof(int)*(N+2));
        fill(Tree,Tree+(N+2),0);
        for(int i=1;i<=N;i++)
        {
            scanf("%I64d",&ToTran[i].num);
            ToTran[i].doc=i;
        }
        RESET(ToTran,Array);
        printf("%I64d\n",BDans(Array,Tree));
    }
    return 0;
}

之前没有想到可以对数据大小进行改变,而误认为不能用树状数组解决这题,实在对不起之前的读者。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值