第一次知道BIT是在去年暑期培训的时候了,当时听线段树感觉似懂非懂,但至少知道这个线段树它想表达个什么意思,前几天校赛也用一种类似于模拟的手法实现了线段树,然后今天从套题训练又转至专题训练,就对那个暑期培训云里雾里畏之如蛇蝎的树状数组又拿出来看了一眼,然后居然发现能看懂了!!!然后居然发现这玩意儿这么简单???然后居然发现前几天校赛用线段树做我是不是个智障!!!
每天学一丢系列,意为每天学一点丢一点,在此留下学习过程及证明。
树状数组,基本操作为 区间求和 & 单点修改。我们发现当我们需要求数组中前n个元素的和的时候,线段树模型的右子节点都是并不需要的。那我们保留所有的左子节点,就成了我们所说的树状数组。
如果说一开始并没有看懂为什么右子节点不需要的话,从这个图中就很容易看出了。我们在计算1-5的和时,其实就是bit[4]+bit[5]。在计算1-6的和时是bit[4]+bit[6]。在计算1-7的和时是bit[4]+bit[6]+bit[7]。而这种计算恰好是对应编号每次去掉最末尾的1后所产生的新的下标的值之前的和累加而得。(有些绕但是我的博客是给自己看的所以只需要我第二次看到能懂就可以,谢谢刘老师的开导)
而这时引入一种能够快速去掉最末尾1的神奇方法,也是我们常说的位运算 i -= i&-i 或 i = i&(i-1)。同样,可以发现,我们改变一个值之后,所会影响到的所有的值其实是在当前最末尾1出加一后所得到的新的下标。即 i += i&-i; 基本操作完成。
int bit[maxn]; //1~n
int sum(int i)
{
int s = 0;
while(i > 0)
{
s += bit[i];
i -= i&-i;
// i = i&(i-1);
}
return s;
}
void add(int i, int x)
{
while(i <= n)
{
bit[i]+=x;
i += i&-i;
}
}
emmm,到此为止一个一维的BIT就已经结束了,是不是觉得比线段树简单了不少....emmm,是的。
这个时候就把眼光放回到之前校赛中的那个题,我用模拟的手敲线段树的那道题。校赛某题。我们每次既然只需要1-n的值,那么就直接用树状数组不好吗!于是就有了这么一个不到十分钟就敲出来的代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
const int mod = 100000007;
int a[maxn];
int sum(int i)
{
int ans = 0;
while(i>0)
{
ans = (ans+a[i])%mod;
i -= i&-i;
}
return ans;
}
void add(int i, int x)
{
while(i<maxn)
{
a[i] = (a[i]+x)%mod;
i += i&-i;
}
}
int main(){
int T;
scanf("%d", &T);
while(T--)
{
memset(a, 0, sizeof a);
int n, num;
scanf("%d", &n);
for(int i=0; i<n; i++)
{
scanf("%d", &num);
int cnt = sum(num);
//cout<<cnt<<endl;
add(num, cnt+1);
}
printf("%d\n", sum(n));
}
}
区区不到50行就解决了之前拿着棒槌敲脑袋都解决不了的问题...瞬间觉得自己就是个沾沾自喜的智障。另附手敲线段树和BIT代码的差距。
不论是从时间还是从内存还是从长度全部完爆智障的自己.....BIT的储存空间是O(n)的,其实只比线段树小了一个常数,每个操作的运行时间来讲应该也都是logn级别的,但是应该是我写的太蠢了加上很多的if判断使得这道题的差距竟然这么的大, 心塞。不过从原来胆小根本不敢去碰到现在开了题并且做出来感觉还是很不错的,同时明白了校赛当时的大佬为什么可以7分钟A出我一个小时的题,Orz。