以前都学过树状数组,但是已经差不多忘记了!不过看一看后,马上就都回忆起来了!而且感觉经过这么久的学习,对树状数组有了更深一层的领悟!个人觉得树状数组在本质上与线段树是没有区别的!都是管理区间,只不过树状数组管理区间的方式不一样罢了!
经过了解后就学习着解决hdu1754那道水题吧!我在网上看到的代码多半是用线段树解决的!这里把用色树状数组的解决代码贴出来吧!
7097586 | 2012-11-06 16:32:30 | Accepted | 1754 | 593MS | 2620K | 1524 B | C++ | xiaoshuai |
- #include<stdio.h>
- #include<string.h>
- #include<algorithm>
- #define M 200005
- using namespace std;
- int a[M];
- int Max[M];
- int hash[M];
- int n,m;
- int lowbit(int x){
- return x&(-x);
- }
- void build(int pos,int val){
- while(pos<=n){
- if(hash[pos]==0){
- Max[pos]=val;
- hash[pos]++;
- }else{
- Max[pos]=max(Max[pos],val);
- }
- pos+=lowbit(pos);
- }
- }
- void update(int pos,int val){
- //修改这元素 影响到的空间的值
- int i,j,k;
- a[pos]=val;//修改原始数组的值
- for(i=pos;i<=n;i+=lowbit(i)){
- Max[i]=a[i];
- if(lowbit(i)>1){
- for(j=i-1,k=i-lowbit(i)+1;j>=k;j-=lowbit(j)){
- Max[i]=max(Max[i],Max[j]);
- }
- }
- }
- }
- int query(int ll,int rr){
- int ans=a[rr];
- while(true){
- ans=max(ans,a[rr]);
- if(ll==rr)break;
- for(rr-=1;rr-ll>=lowbit(rr);rr-=lowbit(rr)){
- ans=max(ans,Max[rr]);
- }
- }
- return ans;
- }
- int main(){
- int i;
- char s[2];
- int ll,rr;
- int ans;
- while(scanf("%d %d",&n,&m)!=EOF){
- memset(hash,0,sizeof(hash));
- for(i=1;i<=n;i++){
- scanf("%d",&a[i]);
- build(i,a[i]);
- }
- for(i=0;i<m;i++){
- scanf("%s %d %d",s,&ll,&rr);
- if(s[0]=='U'){
- update(ll,rr);
- }else{
- ans=query(ll,rr);
- printf("%d\n",ans);
- }
- }
- }
- return 0;
- }
尽管这道题是如此简单,但是依然花了我大半天的时间!看来自己的代码能力还是需要提高啊!查询的方法是借鉴了(博客现在找不到了)!把原始数据加进去操作和更新的操作是我自己写的! 说实话,看懂与自己拍出来的差距真的挺大的!
特别是加入数据的操作,最开始学习树状数组是用电子科大的PPT里面的例子!
就是区间求和:
- #include<stdio.h>
- #include<string.h>
- int n=5;
- int a[6]={0,1,2,3,4,5};
- int sum[6];
- int lowbit(int t){
- return t&-t;
- }
- void update(int pos,int val){
- while(pos<=n){
- sum[pos]+=val;
- pos+=lowbit(pos);
- }
- }
- int query(int pos){
- int ans=0;
- while(pos>0){
- ans+=sum[pos];
- pos-=lowbit(pos);
- }
- return ans;
- }
- int main(){
- memset(sum,0,sizeof(sum));
- for(int i=1;i<=n;i++){
- update(i,a[i]);
- }
- int ans=query(5);
- printf("ans=%d\n",ans);
- return 0;
- }
当时我的思路也仅限于 用数状数组来求和! 学习线段树的时候,求和与求最大值几乎是没有区别的!但是用树状数组我就一下子转不过来了!转不过来的时候,一定要弄清楚的是sum数组中存储的到底是什么?sum[i]表示i管理的区间之和!i管理的区间是哪一段呢?[i-lowbit(i}+1,i] 这个区间自己画一画就出来了!
好了,回到就最值的问题!这里我为什么会用到hash呢?主要是对Max数组的初始化问题,我想如果有负数出现,而把Max数组初始化为0或者-1肯定是不对的!怎么办呢?用hash吧!只要位置i是第一次访问,Max[i]肯定与val是相等的!这样就解决了初始化数据的问题!
真正卡了我好久的地方是update(int pos,int val),就是如果要修改一个值,我们怎么办?
这里就一定要理解清楚树状数组是怎么管理区间的!因为把pos位置的value修改后,Max数组可能是需要改变的!改变什么呢?修改pos位置影响到的区间!比如:pos=8,则Max[8],Max[16]的值都可能发生变化!我当时就是在修改区间的位置一直纠缠着!Max[8]=max(a[8],Max[7],Max[6],Max[4])! 那么怎么按顺序遍历8,7,6,4呢?用到lowbit()!
经过一次又一次的wrong answer,一次又一次的反思,最终得到的update代码如下:
- void update(int pos,int val){
- int i,j,k;
- a[pos]=val;//修改原始数组的值
- for(i=pos;i<=n;i+=lowbit(i)){ //修改这元素 影响到的空间的值
- Max[i]=a[i];//由于a[i]的值已经变化,所以Max[i]的值可能不再是原来的值,需要我们重新求
- if(lowbit(i)>1){//这里k=i-lowbit(i)+1,表示下标i管理的区间的起点即i下标管理区间[k,i]
- for(j=i-1,k=i-lowbit(i)+1;j>=k;j-=lowbit(j)){
- Max[i]=max(Max[i],Max[j]);
- }
- }
- }
- }
学习算法是一个悟的过程!所谓“觉悟”,觉是一个瞬间,悟是一个过程。把所有的觉的瞬间,与长长一生的悟结合起来,你所到达的就是终于看见我的心。这是人生的的觉悟。
慢慢接着悟吧!