8月8号的树状数组:(位运算)

摘自:http://www.cnblogs.com/911/archive/2008/05/20/1203477.html

位运算是指按二进制进行的运算。在系统软件中,常常需要处理二进制位的问题。C语言提供了6个位操作

运算符。这些运算符只能用于整型操作数,即只能用于带符号或无符号的char,short,int与long类型。
C语言提供的位运算符列表:
运算符 含义 描述
& 按位与 如果两个相应的二进制位都为1,则该位的结果值为1,否则为0
| 按位或 两个相应的二进制位中只要有一个为1,该位的结果值为1(如果两个相应的二进制都为0,则该位的结果值为0,否则为1)
^ 按位异或 若参加运算的两个二进制位值相同则为0,否则为1
~ 取反 ~是一元运算符,用来对一个二进制数按位取反,即将0变1,将1变0
<< 左移 用来将一个数的各二进制位全部左移N位,右补0
>> 右移 将一个数的各二进制位右移N位,移到右端的低位被舍弃,对于无符号数,高位补0

下面的一系列介绍:现在还不常用。

常用的:

5、左移运算符(<<)
左移运算符是用来将一个数的各二进制位左移若干位,移动的位数由右操作数指定(右操作数必须是非负

值),其右边空出的位用0填补,高位左移溢出则舍弃该高位。
例如:将a的二进制数左移2位,右边空出的位补0,左边溢出的位舍弃。若a=15,即00001111(2),左移2

位得00111100(2)。
源代码:
#include <stdio.h>
main()
{
 int a=15;
 printf("%d",a<<2);
}
左移1位相当于该数乘以2,左移2位相当于该数乘以2*2=4,15<<2=60,即乘了4。但此结论只适用于该

数左移时被溢出舍弃的高位中不包含1的情况。
    假设以一个字节(8位)存一个整数,若a为无符号整型变量,则a=64时,左移一位时溢出的是0

,而左移2位时,溢出的高位中包含1。

6、右移运算符(>>)
右移运算符是用来将一个数的各二进制位右移若干位,移动的位数由右操作数指定(右操作数必须是非负

值),移到右端的低位被舍弃,对于无符号数,高位补0。对于有符号数,某些机器将对左边空出的部分

用符号位填补(即“算术移位”),而另一些机器则对左边空出的部分用0填补(即“逻辑移位”)。

意:对无符号数,右移时左边高位移入0;对于有符号的值,如果原来符号位为0(该数为正),则左边也是移

入0。如果符号位原来为1(即负数),则左边移入0还是1,要取决于所用的计算机系统。有的系统移入0,有的

系统移入1。移入0的称为“逻辑移位”,即简单移位;移入1的称为“算术移位”。 
例: a的值是八进制数113755: 
   a:1001011111101101 (用二进制形式表示)
   a>>1: 0100101111110110 (逻辑右移时)
   a>>1: 1100101111110110 (算术右移时)
   在有些系统中,a>>1得八进制数045766,而在另一些系统上可能得到的是145766。Turbo C和其他一些C

编译采用的是算术右移,即对有符号数右移时,如果符号位原来为1,左面移入高位的是1。
源代码:
#include <stdio.h>
main()
{
 int a=0113755;
 printf("%d",a>>1);
}

突然发现前面写的一啪啦和树状数组没有半毛钱的关系!!!(其实呢,位运算是最原始,最省时间的运算吧)

接下来的是大BOSS:树状数组。

一开始我以为跟位运算有很大的关系呢。

大BOSS的起源:如果给定一个数组,要你求里面所有数的和,一般都会想到累加。但是当那个数组很大的时候,累加就显得太耗时了,时间复杂度为O(n),并且采用累加的方法还有一个局限,那就是,当修改掉数组中的元素后,仍然要你求数组中某段元素的和,就显得麻烦了。所以我们就要用到树状数组,他的时间复杂度为O(lgn),相比之下就快得多。

大BOSS的长相:(这长相有科学依据的。)

其实这个图是想让我们发现:与其一个一个的求a数组,不如用个如c数组的树(肯定不是二叉树)

之后呢,你要求a数组的和时,就直接从树中找!并且这树有一特点:层层环扣:c1=a1,c2=a1+a2,c3=a3,c4=a1+a2+a3+a4,c5=a5,c6=a5+a6,c7=a7,c8=a1+a2+a3+a4+a5+a6+a7+a8,c9=a9,c10=a9+a10,c11=a11........c16=a1+a2+a3+a4+a5+.......+a16。

之后的一系列经过复杂的计算,简单的证明,就与位运算挂钩了。

int lowbit(int x)
{
return x&(-x);
}

这个是树状数组的核心:由它的存在建立如图c数组的树。

我一直忽略的是:这种树的结构让查找不用接触本体,直接找“根节点”就行了。(好像一般树都有此功能才会节省时间的,为什么我只会看到表面的东西。老了,只能看到肤浅,不能渗透本质啊!难怪追不到女生。。。(女人心,海底针))

那么之后的代码就好理解了。(上面的只是一时兴起所为,看不懂很正常)

吐槽一下:为什么算法中最难的,扯的最远的,最抽象的就是树!而火影中最强的初代火影(柱间,为什么要个木字旁呢?还不如叫个“树间”得了)也是会召唤出几棵树来折腾人,并且全篇不停的灌输这棵“树”的强大。我擦可能这日货也是早年深受ACM的毒害吧。(纯属胡扯)

具体参见:http://www.cnblogs.com/zhangshu/archive/2011/08/16/2141396.html

接下来进入实战阶段:

最简单的:HDU 1541和HDU 1556开始

一个是更新点,求区间和。一个是更新区间,求点值。(反正时刻明确此树状数组的特性和长相)

 1 #include<iostream>
 2 #include<stdio.h>
 3 #include<algorithm>
 4 #include<string.h>
 5 using namespace std;
 6 int tree[100005];
 7 void update(int x,int n)
 8 {
 9     while(x<=100005)
10     {
11         tree[x]+=n;
12         x+=x&(-x);
13     }
14 }
15 int sum(int x)
16 {
17     int sum=0;
18     while(x>0)
19     {
20         sum+=tree[x];
21         x-=x&(-x);
22     }
23     return sum;
24 }
25 int main()
26 {
27     int n,i,a1,a2;
28     while(scanf("%d",&n)!=EOF)
29     {
30         memset(tree,0,sizeof(tree));
31         if(n==0)
32             break;
33         for(i=1;i<=n;i++)
34         {
35             scanf("%d%d",&a1,&a2);
36             update(a1,1);
37             update(a2+1,-1);
38         }
39         for(i=1;i<=n;i++)
40         {
41             if(i<n)
42                 printf("%d ",sum(i));
43             else
44                 printf("%d
",sum(n));
45         }
46     }
47     return 0;
48 }

时刻谨记BOSS的长相,技能,血量。以便能在不掉血的情况下,更快秒掉他!

 1 #include<iostream>
 2 #include<stdio.h>
 3 #include<algorithm>
 4 #include<string.h>
 5 using namespace std;
 6 int tree[35005];
 7 int sum(int x)
 8 {
 9     int sum=0;
10     while(x>0)
11     {
12         sum+=tree[x];
13         x-=x&(-x);
14     }
15     return sum;
16 }
17 void update(int x)
18 {
19     while(x<=35000)
20     {
21         tree[x]++;
22         x+=x&(-x);
23     }
24 }
25 int main()
26 {
27     int n,i,a1,a2,level[35005];
28     while(scanf("%d",&n)!=EOF)
29     {
30         memset(tree,0,sizeof(tree));
31         memset(level,0,sizeof(level));
32         for(i=0;i<n;i++)
33         {
34             scanf("%d%d",&a1,&a2);
35             level[sum(a1+1)]++;//坐标从零开始的,而此树从1开始的
36             update(a1+1);
37         }
38         for(i=0;i<n;i++)
39             printf("%d
",level[i]);
40     }
41     return 0;
42 }

Ultra-QuickSort POJ 2299

这个题就是求逆序数:在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。逆序数为偶数的排列称为偶排列;逆序数为奇数的排列称为奇排列。如2431中,21,43,41,31是逆序,逆序数是4,为偶排列。(逆序数的个数如冒泡法排序进行的次数!)

俩种方法:归并排序和离散化+树状数组

先说下离散化:

我只知道这个题目离散化主要用来:因为数的范围是999999999。而最多500000个数,所以数和数之间的间隔很大,可以处理一下,使数的间隔变小!

如一列数:1,99,10000,34 离散化后:1,3,4,2

那么看代码:

 如果数据的范围是999999999(没有这么大),那么可以直接用现状数组了吧。。。(个人猜测)

 1 #include<iostream>
 2 #include<stdio.h>
 3 #include<algorithm>
 4 #include<string.h>
 5 using namespace std;
 6 int a1[500005],tree[500005];         //排序后从新编号用的
 7 int n;
 8 struct line
 9 {
10     int x;
11     int y;
12 }a[500005];
13 bool comp1(line i,line j)
14 {
15     return i.x<j.x;
16 }
17 void update(int x)
18 {
19     while(x<=n)
20     {
21         tree[x]++;
22         x+=x&(-x);
23     }
24 }
25 int sum(int x)
26 {
27     int sum=0;
28     while(x>0)
29     {
30         sum+=tree[x];
31         x-=x&(-x);
32     }
33     return sum;
34 }
35 int main()
36 {
37     int i;
38     __int64 s;
39     while(scanf("%d",&n)!=EOF)
40     {
41         memset(tree,0,sizeof(tree));
42         s=0;
43         if(n==0)
44             break;
45         for(i=1;i<=n;i++)
46         {
47             scanf("%d",&a[i].x);
48             a[i].y=i;
49         }
50         sort(a+1,a+1+n,comp1);
51         a1[a[1].y]=1;
52         for(i=2;i<=n;i++)
53         {
54             if(a[i].x!=a[i-1].x)
55                 a1[a[i].y]=i;
56             else
57                 a1[a[i].y]=a1[a[i-1].y];
58         }
59         for(i=1;i<=n;i++)
60         {
61             update(a1[i]);
62             s+=(sum(n)-sum(a1[i]));              //每次累加该数前面比他大的数的个数
63         }
64         printf("%I64d
",s);
65     }
66     return 0;
67 }

还有归并排序:(先缓一下)

 1 #include <queue>
 2 #include <stack>
 3 #include <math.h>
 4 #include <stdio.h>
 5 #include <stdlib.h>
 6 #include <iostream>
 7 #include <limits.h>
 8 #include <string.h>
 9 #include <algorithm>
10 #define MAX 500010
11 using namespace std;
12 int a[MAX];
13 int tb[MAX/2],te[MAX/2];
14 long long sum;
15 void Merger(int *a,int beg,int mid,int end)
16 {
17     int k;
18     int n1 = mid - beg + 1;
19     int n2 = end - mid;
20     int i = 0,j = 0;
21     for(k=beg; k<=mid; k++)
22         tb[i++] = a[k];
23     tb[n1] = INT_MAX;
24 
25     for(k=mid+1; k<=end; k++)
26         te[j++] = a[k];
27     te[n2] = INT_MAX;
28 
29     i = j = 0;
30 
31     for(k=beg; k<=end; k++)
32         if( tb[i] <= te[j] )
33             a[k] = tb[i++];
34         else
35         {
36             sum += ( n1 - i );
37             a[k] = te[j++];
38         }
39 }
40 void Mergersort(int *a,int beg,int end)
41 {
42     if( beg < end )
43     {
44         int mid = (beg+end)/2;
45         Mergersort(a,beg,mid);
46         Mergersort(a,mid+1,end);
47         Merger(a,beg,mid,end);
48     }
49 }
50 int main()
51 {
52     int n,i;
53     while( ~scanf("%d",&n) && n )
54     {
55         sum = 0;
56         for(i=0; i<n; i++)
57             scanf("%d",&a[i]);
58         Mergersort(a,0,n-1);
59         printf("%lld
",sum);
60     }
61 return 0;
62 }
原文地址:https://www.cnblogs.com/tt123/p/3249095.html