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\n",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\n",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\n",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\n",sum); 60 } 61 return 0; 62 }