1886: 士兵杀敌(四)
时间限制: 2 Sec 内存限制: 64 MB[提交][状态][讨论版]
题目描述
南将军麾下有百万精兵,现已知共有M个士兵,编号为1~M,每次有任务的时候,总会有一批编号连在一起人请战(编号相近的人经常在一块,相互之间比较熟悉),最终他们获得的军功,也将会平分到每个人身上,这样,有时候,计算他们中的哪一个人到底有多少军功就是一个比较困难的事情,军师小工的任务就是在南将军询问他某个人的军功的时候,快速的报出此人的军功,请你编写一个程序来帮助小工吧。
假设起始时所有人的军功都是0.
输入
只有一组测试数据。 每一行是两个整数T和M表示共有T条指令,M个士兵。(1<=T,M<=1000000) 随后的T行,每行是一个指令。 指令分为两种: 一种形如 ADD 100 500 55 表示,第100个人到第500个人请战,最终每人平均获得了55军功,每次每人获得的军功数不会超过100,不会低于-100。 第二种形如: QUERY 300 表示南将军在询问第300个人的军功是多少。
输出
对于每次查询输出此人的军功,每个查询的输出占一行。
样例输入
4 10
ADD 1 3 10
QUERY 3
ADD 2 6 50
QUERY 3
样例输出
10
60
提示
nyoj123
来源
来自百科:
树状
数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个
元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
这种数据结构(算法)并没有C++和Java的库支持,需要自己手动实现。在Competitive Programming的竞赛中被广泛的使用。树状数组和线段树很像,但能用树状数组解决的问题,基本上都能用线段树解决,而线段树能解决的树状数组不一定能解决。相比较而言,树状数组效率要高很多。
假设数组a[1..n],那么查询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有一个快捷的办法,定义一个函数如下即可:
1
2
3
|
int
lowbit(
int
x){
return
x&(x^(x–1));
}
|
利用机器补码特性,也可以写成:
1
2
3
|
int
lowbit(
int
x){
return
x&-x;
}
|
当想要查询一个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的过程。
对于
数组求和来说树状数组简直太快了!
注:
求lowbit(x)的建议公式:
lowbit(x):=x and -x;
或lowbit(x):=x and (x xor (x - 1));
lowbit(x)即为2^k的值。
#include<stdio.h>
int T,M;
int tree[1000010];//即为本题大佬树状数组,记录的是节点管辖区域之内的所有节点之和
int lowbit(int x){//求的是节点x的管辖区间即2^k(k为x的二进制末尾0的个数)
return x&(-x);
}
void add(int i,int x){//修改:给数组中的第i个元素加上x
while(i<=M){//M个人
tree[i]+=x;
i+=lowbit(i); //emmmm…加上x的管辖区域
}
}
int Sum(int i){//查询:查询的是从第一个节点到当前节点(i)的总和
int sum=0;
while(i>0){
sum+=tree[i];
i-=lowbit(i);
}
return sum;
}
int main(){
int a,b,c;
char str[5];//接收指令用
scanf("%d %d",&T,&M);
for(int i=0;i<T;i++){
scanf("%s",str);
if(str[0]=='A'){
scanf("%d %d %d",&a,&b,&c);
add(a,c);
add(b+1,-c);//第a项加c,对于Sum来说相当于a到M所有的项都加c
//同理,第b+1项加-c对于Sum来说相当于b+1到M所有的项都加-c
//两个操作对于Sum来说相当于第a项到第b项都加上c,于是Sum(a到b)存的也都是c ,即使那一项的值
}
else if(str[0]=='Q'){
scanf("%d",&c);
printf("%d\n",Sum(c));
}
}
return 0;
}
只能说:太巧妙了,智商限制了我的想象