线段树是一个很好的维护区间关系的这样的一个数据结构,但是,很多时候我们可以用更小空间、更快速度的数据结构来维护一个前缀关系。
树状数组详解:数据结构——树状数组篇
顺便借张图:c数组所代表的范围都在图上了
树状数组的主角:x的二进制表达式中最低位的1所对应的值,比如0110对应的是0010,它除了x最低
的1的位置上是1,其余位上都是0,两种求法:
#define lowbit(x) ( x&(-x) )
#define lowbit(x) (x - (x & (x - 1)))
还是
以
维
护
区
间
和
为
例
\color{red}{以维护区间和为例}
以维护区间和为例说基本操作吧
//更新和查询的时候操作相反,一个一直加lowbit(x),一个一直减lowbit(x)
更新:
O
(
l
o
g
n
)
O(logn)
O(logn)
手动模拟的修改相应值要更新的序号
1(0001)—— A[1]、A[2]、A[4]、A[8];
2(0010)—— A[2]、A[4]、A[8];
3(0011)—— A[3]、A[4]、A[8];
4(0100)—— A[4]、A[8];
5(0101)—— A[5]、A[6]、A[8];
6(0110)—— A[6]、A[8];
7(0111)—— A[7]、A[8];
8(1000)—— A[8];
有点像线段树里面往上回溯,修改上面的值,树状数组是一直回溯到最大的那个值
void add(int x,int val)//第x为给它加上val的权
{
//预防x=0的时候出现死循环,还有后面也要记得加上c[0]
//更多的时候习惯给所有进入更新操作的数都加上1,就不用担心出现0了,这时候N也要加1
if(!x)
{
c[x]+=val;
return;
}
while(x<=N)//N是边界值
{
c[x]+=val;
x+=x&(-x);
}
}
查询: O ( l o g x ) O(logx) O(logx)
手动模拟的修改相应值要更新的序号
1 = (0001)—— Q[1] = A[1];
2 = (0010)—— Q[2] = A[2];
3 = (0011)—— Q[3] = A[3] + A[2];
4 = (0100)—— Q[4] = A[4];
5 = (0101)—— Q[5] = A[5] + A[4];
6 = (0110)—— Q[6] = A[6] + A[4];
7 = (0111)—— Q[7] = A[7] + A[6] + A[4];
8 = (1000)—— Q[8] = A[8];
凭直觉可以得出(找规律),一直减lowbit(x)就可以了
LL query(int x)
{
LL ans=c[0];
//如果没有c[0]的话,直接赋为0就行了
while(x)
{
ans+=c[x];
x^=x&(-x);
}
return ans;
}
基本操作就这两个了,注意:
1、注意0,会造成更新操作中的死循环
2、N的值可以比边界值大,但不能小,如果更新操作中数的值都加上了1,N也要加1
3、之前做题犯的一个小错误:查询操作中,都知道如果要求前x个数的和,就要从c[x]开始加,比如前7个数的和=c[7]+c[6]+c[4],但是如果因为题目要求不同不需要第x位上的数据,这时候不是不加c[x]就好了,而是要让x先减1,这样算前x-1个数的和(因为c[x]≠A[x])
例1(一个树状数组的入门题):POJ 2352 Stars
感觉题目的引导挺好的,每个左边都是按一定顺序输入的,这样就可以保证后面星星的level就是之前出现过的星星中,横坐标x小于等于i星横坐标的那些星星的总数量。
不过最开始我想半天还是没有想出了怎么解,,,,,,,,,
再看几个题感受一下树状数组的入门套路(难度顺序差不多也是这样):
例2:POJ 2481 Cows
例3:POJ 3067 Japan
(不知道为什么两个城市之间铁路也可以建两条)
例4:HDU 1540 Tunnel Warfare
(也可以用STL做,stack和set)
例6:POJ 2299 Ultra-QuickSort
(树状数组求逆序数,离散化)
例7:POJ 3416——Crossing
(发现有时候也可以写几个树状数组)
感觉一般都是两个变量,将一个变量进行一些操作,变成为对求解有利的条件,就像星星那个题里面y升序就保证了,后面比当前x小的数量和就是星星的level
例2代码:
#include <iostream>
#include <string.h>
#include<stdio.h>
#include<string>
#include<algorithm>
typedef long long LL;
#define N 20000
using namespace std;
const int manx=2e5+10;
const int INF=0x3f3f3f3f;
int n,c[manx];
struct node
{
int l,r,pos,num;
} cow[manx];
int cmp(node a,node b)
{
if(a.l==b.l)
return a.r>b.r;
return a.l<b.l;
}
int cmp1(node a,node b)
{
return a.pos<b.pos;
}
void add(int x)
{
while(x<=100001)
{
c[x]++;
x+=x&(-x);
}
}
int query(int x)
{//比小的数,不是不加c[x]就行了
x--;
int ans=0;
while(x)
{
ans+=c[x];
x^=x&(-x);
}
return ans;
}
int main()
{
while(scanf("%d",&n),n)
{
memset(c,0,sizeof(c));
for(int i=0; i<n; i++)
{
cow[i].pos=i;
scanf("%d%d",&cow[i].l,&cow[i].r);
cow[i].l++;
cow[i].r++;
}
sort(cow,cow+n,cmp);
add(cow[0].r);
cow[0].num=0;
for(int i=1; i<n; i++)
{
if(cow[i].l==cow[i-1].l&&cow[i].r==cow[i-1].r)
cow[i].num=cow[i-1].num;
else
cow[i].num=i-query(cow[i].r);
add(cow[i].r);
}
sort(cow,cow+n,cmp1);
for(int i=0; i<n-1; i++)
printf("%d ",cow[i].num);
printf("%d\n",cow[n-1].num);
}
}