(树状数组)hdu1166 敌兵布阵

今天学习了树状数组,看了能解决的问题之后觉得跟线段树非常像,不过后来了解到线段树的用途其实很广泛,如单点更新、区间更新、区间查询、最值查询等问题,都能够高效的解决。而树状数组则是一种空间占用率很低、且算法效率高(O(logn))的一种数据结构。并且,代码很简洁,只用短短几行代码便完成了所有的操作。

首先是lowbit操作——树状数组的核心。这个操作的作用是把一个二进制数变为只保留最低位的1后形成的数,如110010——变成10。实现方法是,由计算机补码运算的原理,对一个数取相反数相当于逐位取反再加1,再用这个数本身和他的相反数做逐位与(&)操作,即得到最后一位1,具体代码如下:

int lowbit(int k) {//树状数组基本操作,取一个数(二进制)最低位的1
    return k & (-k);//(由计算机补码原理,一个数取负相当于这个数逐位取反再加1
}

以下是更新、求和操作:

单点更新:

void update(int pos, int num) {//从pos到n,更新节点的值
    while(pos <= n) {
        a[pos] += num;//更新
        pos += lowbit(pos);//向上更新受到影响的祖先结点
    }
    return;
}

求和:

int sum(int end) {//求一段区间的和
    int sum = 0;
    while(end > 0) {
        sum += a[end];
        end -= lowbit(end);
    }
    return sum;
}

有了这些基本操作之后,用这道题,hdu1166——敌兵布阵,练了一下手。昨天用线段树做出了它,现在改用树状数组实现,代码简洁易懂。

题意:一个数组a[n],有如下三个操作:

(1) Add i j,i和j为正整数,表示第i个数增加j

(2)Sub i j ,i和j为正整数,表示第i个数减少j(j不超过30);

(3)Query i j ,i和j为正整数,i<=j,表示询问ai到aj的和;

简单的树状数组应用,加减操作可以通过单点更新来实现,查询区间和可由sum(b)-sum(a-1)得到。具体代码如下:

#include<bits/stdc++.h>
typedef long long ll;

const int maxn = 50010;

int t, n;
int a[maxn];

int lowbit(int k) {//树状数组基本操作,取一个数(二进制)最低位的1
    return k & (-k);//(由计算机补码原理,一个数取负相当于这个数逐位取反再加1
}

void update(int pos, int num) {//从pos到n,更新节点的值
    while(pos <= n) {
        a[pos] += num;//更新
        pos += lowbit(pos);//向上更新受到影响的祖先结点
    }
    return;
}

int sum(int end) {//求一段区间的和
    int sum = 0;
    while(end > 0) {
        sum += a[end];
        end -= lowbit(end);
    }
    return sum;
}

int main() {
    while(~scanf("%d", &t)) {
        for(int cas = 1;cas <= t;cas++) {
            scanf("%d", &n);
            int m;
            memset(a, 0, sizeof(a));
            for(int i = 1;i <= n;i++) {
                scanf("%d", &m);
                update(i, m);//每输一个数,更新一次,建立树状数组
            }
            char str[10];
            printf("Case %d:\n", cas);
            while(scanf("%s", str), strcmp(str, "End")) {
                int a1, a2;
                if(strcmp(str, "Add") == 0) {
                    scanf("%d %d", &a1, &a2);
                    update(a1, a2);//向上更新
                }
                else if(strcmp(str, "Sub") == 0) {
                    scanf("%d %d", &a1, &a2);
                    update(a1, -a2);//向上更新
                }
                else if(strcmp(str, "Query") == 0) {
                    scanf("%d %d", &a1, &a2);
                    printf("%d\n", sum(a2) - sum(a1-1));//b到a-1即为a到b段的和
                }
            }
        }
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值