因为是临时准备参加省赛,队友都使用c++,所以我也必须使用c++,一直以来我都是java编码更顺手,c++大量的编码实践还是缺乏的,所以这正是一个磨练的机会,果断上去。
该题目有两种做法,我看到题目的时候只想到一种,没有想到第二种,后来同队的人说有第二种,让我到网上看,后深入的看了网友们不同的做法和见解,才了解到还有第二种。
做这个题目的时候发现两个问题,第一cin、cout还是没有scanf和printf来的快,这点在ac中得到验证,另外就是多余的语句会同样增加速度。我因为只想到一种方法,因此努力改进编码,最后过了,后来看到第二种解法,眼睛瞪得老大啦~~
士兵杀敌(一)
- 描述
-
南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的。
小工是南将军手下的军师,南将军现在想知道第m号到第n号士兵的总杀敌数,请你帮助小工来回答南将军吧。
注意,南将军可能会问很多次问题。
- 输入
- 只有一组测试数据
第一行是两个整数N,M,其中N表示士兵的个数(1<N<1000000),M表示南将军询问的次数(1<M<100000)
随后的一行是N个整数,ai表示第i号士兵杀敌数目。(0<=ai<=100)
随后的M行每行有两个整数m,n,表示南将军想知道第m号到第n号士兵的总杀敌数(1<=m,n<=N)。 - 输出
- 对于每一个询问,输出总杀敌数
每个输出占一行 - 样例输入
-
5 2 1 2 3 4 5 1 3 2 4
- 样例输出
-
6 9
我的代码:
1 //#include<iostream> 2 #include<stdio.h> 3 using namespace std; 4 5 int main() 6 { 7 int n,m,tmp; 8 scanf("%d%d",&n,&m); 9 10 int a[n+1]; 11 a[0]=0; 12 13 for(int i=1;i<n+1;i++){ 14 scanf("%d",&tmp); 15 16 a[i]=a[i-1]+tmp; 17 } 18 for(int i=0;i<m;i++) 19 { int b,d; 20 scanf("%d%d",&b,&d); 21 22 if(b==1) printf("%d\n",a[d]); 23 else 24 { 25 printf("%d\n",a[d]-a[b-1]); 26 } 27 28 29 } 30 31 return 0; 32 }
最优程序:
1 2 #include<cstdio> 3 const int MAX=1000010; 4 int sum[MAX]; 5 int main() 6 { 7 int N,q,m,n; 8 scanf("%d%d",&N,&q); 9 for(int i=1;i<=N;++i) 10 { 11 scanf("%d",&sum[i]); 12 sum[i]+=sum[i-1]; 13 } 14 for(int i=0;i!=q;++i) 15 { 16 scanf("%d%d",&m,&n); 17 printf("%d\n",sum[n]-sum[m-1]); 18 } 19 }
第二种解法,用到树状数组
树状数组是一个查询和修改复杂度都为log(n)的数据结构,假设数组a[1..n],
用lowbit函数维护了一个树的结构
那么查询a[1]+...+a[n]的时间是log级别的,而且是一个在线的数据结构,
支持随时修改某个元素的值,复杂度也为log级别。
来观察这个图:
令这棵树的结点编号为C1,C2...Cn。令每个结点的值为这棵树的值的总和,那么容易发现:
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
...
C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16
这里有一个有趣的性质:
设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,
所以很明显:Cn = A(n – 2^k + 1) + ... + An
算这个2^k有一个快捷的办法,定义一个函数如下即可:
int lowbit(int x){
return x&(x^(x–1));
}
当想要查询一个SUM(n)(求a[n]的和),可以依据如下算法即可:
step1: 令sum = 0,转第二步;
step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步;
step3: 令n = n – lowbit(n),转第二步。
可以看出,这个算法就是将这一个个区间的和全部加起来,为什么是效率是log(n)的呢?以下给出证明:
n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。
那么修改呢,修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。
所以修改算法如下(给某个结点i加上x):
step1: 当i > n时,算法结束,否则转第二步;
step2: Ci = Ci + x, i = i + lowbit(i)转第一步。
i = i +lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程。
数组求和对于树状数组简直太快了!
第二种做法:
1 #include"stdio.h" 2 #include<string.h> 3 int a[1000000]; 4 int main() 5 { 6 int n,sum; 7 scanf("%d%d",&n,&sum); 8 int i,j,k; 9 memset(a,0,sizeof(a)); 10 for(i=1;i<=n;i++) 11 { 12 int num; 13 scanf("%d",&num); 14 j=i; 15 while(j<=n) 16 { 17 a[j]=a[j]+num; 18 j+=j&(-j); 19 } 20 } 21 for(i=0;i<sum;i++) 22 { 23 scanf("%d%d",&k,&j); 24 int s1=0,s2=0; 25 k=k-1; 26 while(k>=1) 27 { 28 s1=s1+a[k]; 29 k-=k&(-k); 30 } 31 while(j>=1) 32 { 33 s2=s2+a[j]; 34 j-=j&(-j); 35 } 36 printf("%d",s2-s1); 37 putchar('\n'); 38 } 39 return 0; 40 }