1166-敌兵布阵(树状数组)
来源:互联网 发布:php java des加密解密 编辑:程序博客网 时间:2024/06/05 12:31
http://www.cnblogs.com/justforgl/archive/2012/07/27/2612364.html
现在来说明下树状数组是什么东西?假设序列为A[1]~A[8]
网络上面都有这个图,但是我将这个图做了2点改进。
(1)图中有一棵满二叉树,满二叉树的每一个结点对应A[]中的一个元素。
(2)C[i]为A[i]对应的那一列的最高的节点。
现在告诉你:序列C[]就是树状数组。
那么C[]如何求得?
C[1]=A[1];
C[2]=A[1]+A[2];
C[3]=A[3];
C[4]=A[1]+A[2]+A[3]+A[4];
C[5]=A[5];
C[6]=A[5]+A[6];
C[7]=A[7];
C[8]= A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
以上只是枚举了所有的情况,那么推广到一般情况,得到一个C[i]的抽象定义:
因为A[]中的每个元素对应满二叉树的每个叶子,所以我们干脆把A[]中的每个元素当成叶子,那么:C[i]=C[i]的所有叶子的和。
现在不得不引出关于二进制的一个规律:
先仔细看下图:
将十进制化成二进制,然后观察这些二进制数最右边1的位置:
1 --> 00000001
2 --> 00000010
3 --> 00000011
4 --> 00000100
5 --> 00000101
6 --> 00000110
7 --> 00000111
8 --> 00001000
1的位置其实从我画的满二叉树中就可以看出来。但是这与C[]有什么关系呢?
接下来的这部分内容很重要:
在满二叉树中,
以1结尾的那些结点(C[1],C[3],C[5],C[7]),其叶子数有1个,所以这些结点C[i]代表区间范围为1的元素和;
以10结尾的那些结点(C[2],C[6]),其叶子数为2个,所以这些结点C[i]代表区间范围为2的元素和;
以100结尾的那些结点(C[4]),其叶子数为4个,所以这些结点C[i]代表区间范围为4的元素和;
以1000结尾的那些结点(C[8]),其叶子数为8个,所以这些结点C[i]代表区间范围为8的元素和。
扩展到一般情况:
i的二进制中的从右往左数有连续的x个“0”,那么拥有2^x个叶子,为序列A[]中的第i-2^x+1到第i个元素的和。
终于,我们得到了一个C[i]的具体定义:
C[i]=A[i-2^x+1]+…+A[i],其中x为i的二进制中的从右往左数有连续“0”的个数。
第03讲 利用树状数组求前i个元素的和S[i]
理解了C[i]后,前i个元素的和S[i]就很容易实现。
从C[i]的定义出发:
C[i]=A[i-2^x+1]+…+A[i],其中x为i的二进制中的从右往左数有连续“0”的个数。
我们可以知道:C[i]是肯定包括A[i]的,那么:
S[i]=C[i]+C[i-2^x]+…
也许上面这个公式太抽象了,因为有省略号,我们拿一个具体的实例来看:
S[7]=C[7]+C[6]+C[4]
因为C[7]=A[7],C[6]=A[6]+A[5],C[4]=A[4]+A[3]+A[2]+A[1],所以S[7]=C[7]+C[6]+C[4]
现在直接告诉你结论:2^x=i&(-i)
证明:设A’为A的二进制反码,i的二进制表示成A1B,其中A不管,B为全0序列。那么-i=A’0B’+1。由于B为全0序列,那么B’就是全1序列,所以-i=A’1B,所以:
i&(-i)= A1B& A’1B=1B,即2^x的值。
x 为 i 的二进制的最后一位1之后0的个数
假如 i=7 ,二进制0000 0111 ,那么x=0,s[7]=c[7]+c[7-2^0]+...
i=6 0000 0110 x=1 s[7]=c[7]+c[7-2^0]+c[6-2^1]
i=4 0000 0100 x=2 s[7]=c[7]+c[7-2^0]+c[6-2^1]+c[4-2^2]
s=c[7]+c[6]+c[4];
i -= 4-2^2; //不满足i>0的条件退出循环
int Sum(int i) //返回前i个元素和
{
int s=0;
while(i>0)
{
s+=C[i];
i-=i&(-i);
}
return s;
}
第04讲 更新C[](更新原理同上)
正如第01讲提到的小石块问题,如果数组A[i]被更新了怎么办?那么如何改动C[]?
如果改动C[]也需要O(n)的时间复杂度,那么树状数组就没有任何优势。所以树状数组在改动C[]上面的时间效率为O(logn),为什么呢?
因为改动A[i]只需要改动部分的C[]。这一点从第02讲的图中就可以看出来:
如上图:
假如A[3]=3,接着A[3]+=1,那么哪些C[]需要改变呢?
答案从图中就可以得出:C[3],C[4],C[8]。因为这些值和A[3]是有联系的,他们用树的关系描述就是:C[3],C[4],C[8]是A[3]的祖先。
那么怎么知道那些C[]需要变化呢?
我们来看“A”这个结点。这个“A”结点非常的重要,因为他体现了一个关系:A的叶子数为C[3]的2倍。因为“A”的左子树和右子树的叶子数是相同的。 因为2^x代表的就是叶子数,所以C[3]的父亲是A,A的父亲是C[i+2^0],即C[3]改变,那么C[3+2^0]也改变。
我们再来看看“B”这个结点。B结点的叶子数为2倍的C[6]的叶子数。所以B和C[6+2^1]在同一列,所以C[6]改变,C[6+2^1]也改变。
推广到一般情况就是:
如果A[i]发生改变,那么C[i]发生改变,C[i]的父亲C[i+2^x]也发生改变。
这一行的迭代过程,我们可以写出当A[i]发生改变时,C[]的更新函数为:
void Update(int i,int value) //A[i]的改变值为value
{
while(i<=n)
{
C[i]+=value;
i+=i&(-i);
}
}
#include <bits/stdc++.h>
using namespace std;
int a[10005];
int n;
int lowbit(int t)
{
return t&(-t);
}
void insert(int t, int d)
{
while(t<=n)
{
a[t]+=d;
t+=lowbit(t);
}
}
int getsum(int t)
{
int sum = 0;
while(t>0)
{
sum+=a[t];
t-=lowbit(t);
}
return sum;
}
int main() {
int T, i, j, k, t;
scanf("%d", &T);
t = 0;
while(T--) {
memset(a, 0, sizeof(a));
scanf("%d", &n);
for(i = 1; i <= n; i++) {
scanf("%d", &k);
insert(i, k);
}
char str[10];
scanf("%s", str);
printf("Case %d:\n", ++t);
while(strcmp(str, "End") != 0) {
int x, y;
scanf("%d%d", &x, &y);
if(strcmp(str, "Query") == 0) {
printf("%lld\n", getsum(y) - getsum(x-1));
}
else if(strcmp(str, "Add") == 0) {
insert(x, y);
}
else if(strcmp(str, "Sub") == 0) {
insert(x, (-1) * y);
}
scanf("%s", str);
}
}
return 0;
}
- 敌兵布阵 1166 树状数组
- 1166-敌兵布阵(树状数组)
- 敌兵布阵(树状数组)
- 敌兵布阵-树状数组
- 敌兵布阵 --- 树状数组
- 敌兵布阵 树状数组
- 树状数组--敌兵布阵
- hdu 1166 敌兵布阵 (树状数组)
- (树状数组) hdu 1166 敌兵布阵
- HDU 1166 敌兵布阵【树状数组】
- HDU-1166-敌兵布阵(树状数组)
- hdu 1166 敌兵布阵 <树状数组>
- hdu 1166 敌兵布阵(裸树状数组)
- hdu - 1166 - 敌兵布阵(树状数组)
- hdu 1166 敌兵布阵【树状数组入门】
- hdu 1166 敌兵布阵 树状数组
- hdu hdoj 1166 敌兵布阵 树状数组
- HDU--1166 -- 敌兵布阵 [树状数组]
- Java —— this关键字和内部类
- linux内存使用机制探究笔记
- WinRAR的命令行模式用法介绍
- Unicode字符重命名批处理实现
- HDFS架构与交互
- 1166-敌兵布阵(树状数组)
- javaSE-P5
- 统计键盘录入字符串中字符串大小写和数字的个数
- Ubuntu调节鼠标指针移速
- 典型卷积神经网络结构总结
- Stack Overflow 大规模裁员背后暗藏的危机
- USB、Mini-USB、Micro-USB接口的引脚定义
- 计算字符串中所有数字之和Python版
- 【Linux】常用的文本操作(排序、选取、计数、应用正则表达式查找等)