- 快速求和
如何快速地求区间[x,y]的数组和呢?相信很容易知道,只需要建立一个前缀和数组(Tree[i]的数值为import[1]~import[i]的和),就能在O(1)内求出任意区间的和,若只是求任意区间的和,显然这种方法是最快而且在线的。 - 树状数组的意义
一个简单的前缀和数组虽然能在通常意义下做到最快的求和,对于数组的维护却相当费力。对输入数组的单点修改,简单前缀和数组的维护成本为O(n),那么对于需要频繁更改数据的维护,简单前缀数组是不合适的。
有没有一种方法在修改和求和统计上能够同时做到O(1)呢?很遗憾并没有在所有情况下都这么快的结构。
退而求其次,我们找到了一种在修改和求和统计速度都及其接近O(1)的结构,那就是树状数组。 - 树状数组的由来
想像这样一种树,它是一种修改版的前缀和,这种微妙的修改使得Tree[i]是i之前多个数的和(但不是严格的从1到i的和),这样使得修改的时候没必要修改像简单前缀和一样的次数。但这似乎使得区间求和不太方便,再想象这种性质:Tree[i]是i前k个数的和而这个k是不确定的,它有时比较小,有时候非常大,这使得不论求小区间还是大区间都能很方便。
那这个有时大有时小的数如何控制呢?这就是树状数组的核心:lowbit(x)=x&-x,这是一个与x相关的数,能够恰如其分地控制前缀范围。 - lowbit(x)函数
记n为x能够整除2的次数,那么这个函数的值就是2的n次方。
不难想象,当x为2的k次方时,Tree[lowbit(x)]恰好为1~N所有数的和。当x为奇数时,Tree[lowbit(x)]仅仅是import[x],而x为普通偶数时,lowbit[x]时大时小。 - 代码核心:
参见这篇博客:
代码 示例
输入:
第一行一个数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;
}
之前没有想到可以对数据大小进行改变,而误认为不能用树状数组解决这题,实在对不起之前的读者。。。