线段树和树状数组(L3-002 特殊堆栈 (30 分))
目录
-
概念
-
线段树也可与称为区间树
-
线段树同时也是一个二叉树
-
树上的每个节点对应于一个区间(线段),区间的头和尾都是整数
-
同一层的节点所代表的的区间不会重叠
-
叶子结点的区间就是单位长度,不可拆分
总结:线段树是一棵二叉树,树中的每一个结 点表示了一个区间[a,b]。a,b通常是整数。 每一个叶子节点表示了一个单位区间(长 度为1)。对于每一个非叶结点所表示的 结点[a,b],其左儿子表示的区间为 [a,(a+b)/2],右儿子表示的区间为 [(a+b)/2+1,b] (保留整数部分)。
-
-
图示
区间[1,9]的线段树
解释:(理解即可)
线段树的平分构造,实际上是用了二分的方法。若根节点对应的区间是[a,b],那么它的深度为log2(b-a+1) +1 (向上 取整)。叶子节点的数目和根节点表示区间的长度相同.
线段树节点要么0度,要么2度, 因此若叶子节点数目为N, 则线段树总结点数目为2N-1
-
区间的分解
在区间[1,9]上对[2,8]的分解
分解规则:
如果有某个节点代表的区间, 完全属于待分解区间,则该节点为“终止” 节点,不再继续往下分解,所有“终止”节点所代表的区间都不重叠,且加在一起就恰好等于整个待分解区间 -
应用举例:
给你一个数的序列A1A2…An。 并且可能 多次进行下列两个操作:
1、对序列里面的某个数进行加减
2、询问这个序列里面任意一个连续的子序列AiAi+1…Aj的和是多少。希望第2个操作每次能在log(n)时间内完成方法:
if 如果走到节点[L,R]时,如果要查询的区间就是[L,R] (求AL到AR的和)那么直接返回该节点的Sum,并累加到总的和上;
else 如果不是,则:
对于区间[L,R],取mid=(L+R)/2;
然后看要查询的区间与[L,mid]或[mid+1,R]哪个有交集,就进入哪个区间进行进一步查询。(因为这个线段树的深度最深的LogN,所以每次遍历 操作都在LogN的内完成。但是常数可能很大。)
-
poj相关习题 (可去poj官网查看)
POJ 3264 Balanced Lineup
POJ 3468 A Simple Problem with Integers
POJ 2528 Mayor’s posters
POJ 1151 Atlantis
POJ 3321 Apple Tree
-
定义
树状数组
树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构对于序列a,我们设一个数组C
◦ C[i] = a[i – 2^k + 1] + … + a[i]
◦ k为i在二进制下末尾0的个数
◦ 2^k就是i 保留最右边的1,其余位全变0 ◦ i从1开始算!
C即为a的树状数组 -
k的含义,如何求k?
2^k = lowbit(x) = x&(-x)
通常我们用lowbit(x)表示x对应的2k , lowbit(x) = x&(-x)
lowbit(x) 实际上就是x的二进制表示形式 留下最右边的1,其他位都变成0
举些例子 公式C[i] = a[i-lowbit(i)+1] + …+ a[i]
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图示
-
好处
树状数组的好处在于能快速求任意区间的和a[i] + a[i+1] + … + a[j]。
设sum(k) = a[1]+a[2]+…+a[k]则 a[i] + a[i+1] + … + a[j] = sum(j)-sum(i-1)
有了树状数组,sum(k)就能在O(logN)时间内求出, N是a数组元素个数。而且更新一个a的元素所花的时间也是O(logN)的(因为a更新了C也得更新)。
解释:
根据C的构成规律,可以发现sum(k)可以表示为: sum(k) = C[n1]+C[n2] + …+ C[nm]
其中 nm= k
ni-1 = ni - lowbit(ni) 而且 n1 – lowbit(n1 ) 必须等于0,
n1大于0
如:sum(6) = C[4]+C[6] -
补充
有关于一些树状数组的证明,读者可以自行查询,过程比较复杂。
-
poj相关习题 (可去poj官网查看)
2182, 2352, 1177, 3667,3067
-
lowbit(i)
#define lowbit(i) ((i) & (-i)) //可以直接使用一个宏定义
-
树状数组的区间维护
我们利用数组查找时候O(1)的特点,直接修改对应的数值,即:tree[index] = n。这样是最开始的一步,接下来就是维护了。我们从index开始,根据lowbit进行遍历,即:i += lowbit(i),每次循环就将tree[i] += n,这样就保证的了树状数组的更新。
void updata(int x, int v) { for (int i = x; i < maxn; i += lowbit(i)) { tree[i] += v; } }
跟线段树一样,操作是非常多变的,具体的操作有具体的写法,这里就随便写一个操作了。里面NUM这个全局变量,很明显是数据的总长度,设置成全局变量了,当然作为参数由外面传进来也是可以的。
-
树状数组的区间查询
这里的区间查询很明显能感到是比线段树简单很多的,或者说查找的是前缀和。例如我们查找20,得到的是1-20的和,只能得到前缀无法得到具体的区间值。不过问题不大,我们可以通过求两次取差的方法获取区间值,例如:find(right) - find(left)。不过如果是大量的区间运算,麻烦一点写成线段树更好。
跟更新相反,我们从index开始,利用lowbit进行递减,就可以一直减到1,就可以求出前缀和了。
int getsum(int x) { int sum = 0; for (int i = x; i >= 1; i -= lowbit(i)) { sum += tree[i]; } return sum; }
-
模版
#include<iostream> #include<string> using namespace std; int tree[100010]; int NUM = 100005; void update(int index,int n){ for(int i = index;i < NUM;i += (i&-i)){ tree[i] += n; } } int query_sum(int n){ int ans = 0; for(int i = n;i > 0;i -= (i&-i)){ ans += tree[i]; } return ans; } int main() { system("pause"); return 0; }
L3-002 特殊堆栈
堆栈是一种经典的后进先出的线性结构,相关的操作主要有“入栈”(在堆栈顶插入一个元素)和“出栈”(将栈顶元素返回并从堆栈中删除)。本题要求你实现另一个附加的操作:“取中值”——即返回所有堆栈中元素键值的中值。给定 N 个元素,如果 N 是偶数,则中值定义为第 N/2 小元;若是奇数,则为第 (N+1)/2 小元。
#include<iostream>
#include<stack>
#define lowbit(i) ((i) & (-i))
using namespace std;
const int maxn = 1e5 + 1;
int tree[maxn]; //记录的是数i的位置
stack<int >cp;
void updata(int x, int v) {
for (int i = x; i < maxn; i += lowbit(i)) {
tree[i] += v;
}
}
int getsum(int x) {
int sum = 0;
for (int i = x; i >= 1; i -= lowbit(i)) {
sum += tree[i];
}
return sum;
}
void PeekMedian() {
int left = 1, right = maxn;
int mid ,k = int((cp.size() + 1) / 2);
while (left < right) {
mid = (left + right) / 2;
if (getsum(mid) >= k) {
right = mid;
} else {
left = mid + 1;
}
}
cout << left << endl;
}
int main() {
int n,key;
string s;
cin >> n;
while (n--) {
cin >> s;
if (s == "Push") {
cin >> key;
cp.push(key);
updata(key, 1);
} else if (s == "Pop") {
if (cp.empty()) {
cout << "Invalid" << endl;
} else {
int tem = cp.top();
updata(tem, -1);
cout << tem << endl;
cp.pop();
}
} else if (s == "PeekMedian") {
if(cp.empty()) {
cout << "Invalid" << endl;
} else {
PeekMedian();
}
}
}
return 0;
}
其他类似习题 hdu1166