线段树和树状数组(L3-002 特殊堆栈 (30 分))

目录

1.线段树
2.树状数组
3.代码实现
4.例题


线段树

返回目录

  1. 概念

    • 线段树也可与称为区间树

    • 线段树同时也是一个二叉树

    • 树上的每个节点对应于一个区间(线段),区间的头和尾都是整数

    • 同一层的节点所代表的的区间不会重叠

    • 叶子结点的区间就是单位长度,不可拆分

      总结:线段树是一棵二叉树,树中的每一个结 点表示了一个区间[a,b]。a,b通常是整数。 每一个叶子节点表示了一个单位区间(长 度为1)。对于每一个非叶结点所表示的 结点[a,b],其左儿子表示的区间为 [a,(a+b)/2],右儿子表示的区间为 [(a+b)/2+1,b] (保留整数部分)。

  2. 图示

    区间[1,9]的线段树
    在这里插入图片描述

    解释:(理解即可)
    线段树的平分构造,实际上是用了二分的方法。若根节点对应的区间是[a,b],那么它的深度为log2(b-a+1) +1 (向上 取整)。

    叶子节点的数目和根节点表示区间的长度相同.

    线段树节点要么0度,要么2度, 因此若叶子节点数目为N, 则线段树总结点数目为2N-1

  3. 区间的分解

    在区间[1,9]上对[2,8]的分解

    在这里插入图片描述

    分解规则:
    如果有某个节点代表的区间, 完全属于待分解区间,则该节点为“终止” 节点,不再继续往下分解,所有“终止”节点所代表的区间都不重叠,且加在一起就恰好等于整个待分解区间

  4. 应用举例:

    给你一个数的序列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的内完成。但是常数可能很大。)

  5. 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


树状数组

返回目录

  1. 定义

    树状数组
    树状数组(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的树状数组

  2. 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

    图示
    在这里插入图片描述

  3. 好处

    树状数组的好处在于能快速求任意区间的和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]

  4. 补充

    有关于一些树状数组的证明,读者可以自行查询,过程比较复杂。

  5. poj相关习题 (可去poj官网查看)

    2182, 2352, 1177, 3667,3067


代码实现

返回目录

  1. lowbit(i)

    #define lowbit(i) ((i) & (-i)) //可以直接使用一个宏定义
    
  2. 树状数组的区间维护

    我们利用数组查找时候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这个全局变量,很明显是数据的总长度,设置成全局变量了,当然作为参数由外面传进来也是可以的。

  3. 树状数组的区间查询

    这里的区间查询很明显能感到是比线段树简单很多的,或者说查找的是前缀和。例如我们查找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;
    }
    
  4. 模版

    #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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值