树状数组求区间最大值

来源:互联网 发布:黑客网站源码 编辑:程序博客网 时间:2024/05/16 12:21

讲这个的博文已经不少了,但感觉不够详细不够通俗易懂,所以我尝试着更详细更通俗易懂的说一下我的理解。

 

这个算法只支持单点修改和区间查询最值。每一次维护和查询的时间复杂度都是O((logn)^2),但这是满打满算的时间复杂度。

假设是要维护和查询区间的最大值(最小值将max改成min 就好了)

这个算法和树状数组维护和查询区间和的方法很相似:

 

一、数组的含义

1、在维护和查询区间和的算法中,h[x]中储存的是[x,x-lowbit(x)+1]中每个数的和,

 

2、在求区间最值的算法中,h[x]储存的是[x,x-lowbit(x)+1]中没个数的最大值。

求区间最值的算法中还有一个a[i]数组,表示第i个数是多少。

(其中lowbit(x) = x & (-x) 这个学过树状数组的应该都知道吧。。。。。)

 

二、单点修改后的更新

1、在维护区间和的算法中,是这样维护单点修改的

[cpp] view plain copy
  1. void updata(int i, int val)  
  2. {  
  3.     while (i <= n)  
  4.     {  
  5.         h[i] += val;  
  6.         i += lowbit(i);  
  7.     }  
  8. }  


 

2、在来看维护区间最大值的算法,我们先看一整段区间[1,n]都需要初始化的情况。(即 h[] 数组都为0,现在需要用 a[] 数组更新 h[] 数组)

[cpp] view plain copy
  1. void updata(int i, int val)  
  2. {  
  3.     while (i <= n)  
  4.     {  
  5.         h[i] = max(h[i], val);  
  6.         i += lowbit(i);  
  7.     }  
  8. }  

这样是可行,于是我们就有了一个O(n*logn)的维护方法,即当要更新一个数的时候,把 h[] 数组清零, 然后用数组 a[] 去更新 h[] 数组。

但这个复杂度显然太高了。

可以发现:对于x,可以转移到x的只有,x-2^0,x-2^1,x-2^2,.......,x-2^k (k满足2^k < lowbit(x)且2^(k+1)>=lowbit(x))

举例:

若 x = 1010000

= 1001000 + lowbit(1001000) = 1001000 + 1000 = 1001000 + 2^3

= 1001100 + lowbit(1001100) = 1001100 + 100 = 1001100 + 2^2

= 1001110 + lowbit(1001110) = 1001110 + 10 = 1001110 + 2^1

= 1001111 + lowbit(1001111) = 1001111 + 1 = 1001111 + 2^0

所以对于每一个h[i],在保证h[1...i-1]都正确的前提下,要重新计算h[i]值的时间复杂度是O(logn),具体方法如下:

[cpp] view plain copy
  1. h[x] = a[x];  
  2. lx = lowbit(x);  
  3. for (i=1; i<lx; i<<=1)  
  4. h[x] = max(h[x], h[x-i]);  
  5. x += lowbit(x);  

这样,我们就可以得到一个和树状数组维护区间合算法很像的算法

[cpp] view plain copy
  1. void updata(int x)  
  2. {  
  3.     int lx, i;  
  4.     while (x <= n)  
  5.     {  
  6.         h[x] = a[x];  
  7.         lx = lowbit(x);  
  8.         for (i=1; i<lx; i<<=1)  
  9.             h[x] = max(h[x], h[x-i]);  
  10.         x += lowbit(x);  
  11.     }         
  12. }  

这个算法的时间复杂度是O((logn)^2),所以现在就可以在O((logn)^2)的时间内完成最值的区间维护了。

 

三、区间查询
1、树状数组求区间合的算法是这样子的:

[cpp] view plain copy
  1. int query(int i)  
  2. {  
  3.     int ans = 0;  
  4.     while (i > 0)  
  5.     {  
  6.         ans += h[i];  
  7.         i -= lowbit(i);  
  8.     }  
  9.     return ans;  
  10. }  


 

2、树状数组求区间最大值:

直接照搬求区间合的方法显然是不行的。

因为区间合中,要查询[x,y]的区间合,是求出[1,x-1]的合与[1,y]的合,然后相减就得出了[x,y]区间的合。

而区间最值是没有这个性质的,所以只能够换一个思路。

设query(x,y),表示[x,y]区间的最大值

因为h[y]表示的是[y,y-lowbit(y)+1]的最大值。

所以,可以这样求解:

若y-lowbit(y) > x ,则query(x,y) = max( h[y] , query(x, y-lowbit(y)) );

若y-lowbit(y) <=x,则query(x,y) = max( a[y] , query(x, y-1);

这个递归求解是可以求出解的,且可以证明这样求解的时间复杂度是O((logn)^2)

具体代码:

[cpp] view plain copy
  1. int query(int x, int y)  
  2. {  
  3.     int ans = 0;  
  4.     while (y >= x)  
  5.     {  
  6.         ans = max(a[y], ans);  
  7.         y --;  
  8.         for (; y-lowbit(y) >= x; y -= lowbit(y))  
  9.             ans = max(h[y], ans);  
  10.     }  
  11.     return ans;  
  12. }  


 

时间复杂度的证明:(换成二进制来看)

因为y经过Logn次变换以后,其与x不同的最高位至少下降了1位,所以最多进行(logn)^2次变换

举例:

y = 1010000

x = 1000001

1010000

=> 1001111 => 1001110 =>1001100 =>1001000

=>1000111 => 1000110 => 1000100

=> 1000011 = > 1000010

=>1000001

=>1000000 < 1000001

 

最后贴上我hdu1754的代码,这一题就是一道单点修改和区间查询最大值的题。

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4. using namespace std;  
  5.   
  6. const int MAXN = 3e5;  
  7. int a[MAXN], h[MAXN];  
  8. int n, m;  
  9.   
  10. int lowbit(int x)  
  11. {  
  12.     return x & (-x);  
  13. }  
  14. void updata(int x)  
  15. {  
  16.     int lx, i;  
  17.     while (x <= n)  
  18.     {  
  19.         h[x] = a[x];  
  20.         lx = lowbit(x);  
  21.         for (i=1; i<lx; i<<=1)  
  22.             h[x] = max(h[x], h[x-i]);  
  23.         x += lowbit(x);  
  24.     }         
  25. }  
  26. int query(int x, int y)  
  27. {  
  28.     int ans = 0;  
  29.     while (y >= x)  
  30.     {  
  31.         ans = max(a[y], ans);  
  32.         y --;  
  33.         for (; y-lowbit(y) >= x; y -= lowbit(y))  
  34.             ans = max(h[y], ans);  
  35.     }  
  36.     return ans;  
  37. }  
  38. int main()  
  39. {  
  40.     int i, j, x, y, ans;  
  41.     char c;  
  42.     while (scanf("%d%d",&n,&m)!=EOF)  
  43.     {  
  44.         for (i=1; i<=n; i++)  
  45.             h[i] = 0;  
  46.         for (i=1; i<=n; i++)  
  47.         {  
  48.             scanf("%d",&a[i]);  
  49.             updata(i);  
  50.         }  
  51.         for (i=1; i<=m; i++)  
  52.         {  
  53.             scanf("%c",&c);  
  54.             scanf("%c",&c);  
  55.             if (c == 'Q')  
  56.             {  
  57.                 scanf("%d%d",&x,&y);  
  58.                 ans = query(x, y);  
  59.                 printf("%d\n",ans);  
  60.             }  
  61.             else if (c == 'U')  
  62.             {  
  63.                 scanf("%d%d",&x,&y);  
  64.                 a[x] = y;  
  65.                 updata(x);  
  66.             }  
  67.         }  
  68.     }  
  69.     return 0;  
  70. }  
原创粉丝点击