首页 > 代码库 > 线段树 基础单点更新 敌兵布阵

线段树 基础单点更新 敌兵布阵

题:敌兵布阵

标准线段树模板代码:

#include<cstdio>
#include<cstring>

const int maxn = 500000 + 10;

struct Node{
    int left, right, count;
}node[maxn];

int a[maxn];

/***********************************
***************建树****************
************i是区间序号************
**l是区间i左边界,r是区间i右边界**
*从1到n开始建树,直到区间长度为1*
**即l=r时,结束。count记录区间和**
************************************/
void maketree(int l, int r, int i){
    node[i].left = l;
    node[i].right = r;
    if(l == r){
        node[i].count = a[l];
        return ;
    }
    int m = (l + r)/2;
    maketree(l, m, 2*i);
    maketree(m + 1, r, 2*i + 1);
    node[i].count = node[2*i].count + node[2*i + 1].count;
}

/****************************************
*****************更新*******************
*i区间序号,x要更新的点。y要更新的值*
*********flag判断更新方式*************
****************************************/
void updatetree(int i, int x, int y, int flag){
    int l = node[i].left;
    int r = node[i].right;
    int m = (l + r)/2;
    if(r == l){
        if(flag)
            node[i].count += y;
        else
            node[i].count -= y;
        return;
    }
    if(x <= m)
        updatetree(2*i, x, y, flag);
    else
        updatetree(2*i + 1, x, y, flag);
    if(flag)
        node[i].count += y;
    else
        node[i].count -= y;
    return;
}

/***********************************
***************查询****************
************************************/
int querytree(int l, int r, int i){
    int m = (node[i].left + node[i].right)/2;
    if(node[i].right <= r && node[i].left >= l) return node[i].count;
    int  ans = 0;
    if(r <= m)
        return querytree(l, r, 2*i);
    else
        if(l > m)
            return querytree(l, r, 2*i + 1);
        else
            return querytree(l, m, 2*i) + querytree(m + 1, r, 2*i + 1);
}

int main(){
    int T, n;
    char str[20];
    scanf("%d", &T);
    for(int i = 1; i <= T; i++){
        printf("Case %d:\n", i);
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        maketree(1, n, 1);
        int x, y;
        while(scanf("%s", str)){
            if(str[0] == 'E') break;
            scanf("%d%d", &x, &y);
            if(str[0] == 'Q') printf("%d\n", querytree(x, y, 1));
            else
                if(str[0] == 'A') updatetree(1, x, y, true);
                    else updatetree(1, x, y, false);
        }
    }
    return 0;
}


优美的线段树代码:

#include <cstdio>
/*************************
****灵活的使用宏定义****
**************************/
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 55555;
int sum[maxn<<2];
void PushUP(int rt) {
	sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
/***********************************
****************建树***************
 此处并没有使用结构体,只是记录了
区间和sum,但l,r与区间序号紧密关联
***********************************/
void build(int l,int r,int rt) {
	if (l == r) {
		scanf("%d",&sum[rt]);
		return ;
	}
	int m = (l + r) >> 1;
	build(lson);
	build(rson);
	PushUP(rt);
}
/***************************************
 *****************更新*****************
 此处没用标记更新方式(加或减),巧妙
 地在调用时处理了正负号,减少函数参数
***************************************/
void update(int p,int add,int l,int r,int rt) {
	if (l == r) {
		sum[rt] += add;
		return ;
	}
	int m = (l + r) >> 1;
	if (p <= m) update(p , add , lson);
	else update(p , add , rson);
	PushUP(rt);
}
/***************************************
******************查询******************
 巧妙地引用了变量ret,减少了对m的讨论
***************************************/
int query(int L,int R,int l,int r,int rt) {
	if (L <= l && r <= R) {
		return sum[rt];
	}
	int m = (l + r) >> 1;
	int ret = 0;
	if (L <= m) ret += query(L , R , lson);
	if (R > m) ret += query(L , R , rson);
	return ret;
}
int main() {
	int T , n;
	scanf("%d",&T);
	for (int cas = 1 ; cas <= T ; cas ++) {
		printf("Case %d:\n",cas);
		scanf("%d",&n);
		build(1 , n , 1);
		char op[10];
		while (scanf("%s",op)) {
			if (op[0] == 'E') break;
			int a , b;
			scanf("%d%d",&a,&b);
			if (op[0] == 'Q') printf("%d\n",query(a , b , 1 , n , 1));
			else if (op[0] == 'S') update(a , -b , 1 , n , 1);
			else update(a , b , 1 , n , 1);
		}
	}
	return 0;
}


上述两种代码思路相同,只是代码风格不同,运行时间,占用内存还是相同的。此题只涉及单点更新和区间求和,所以可以用树状数组求解,代码更简洁,运行速度更快。但树状数组可以求区间和,无法求出区间最值,通用解法仍是用线段树求解。
树状数组的代码:

#include<cstdio>
#include<cstring>

using namespace std;

const int maxn = 50000 + 10;

int len, a[maxn];
char str[50];

int lowbit(int x){
    return x&(-x);
}
/**********************
********更新**********
**********************/
void update(int i, int v){
    while(i <= len){
        a[i] += v;
        i += lowbit(i);
    }
}
/*********************
********求和*********
*********************/
int sum(int i){
    int sum = 0;
    while(i > 0){
        sum += a[i];
        i -= lowbit(i);
    }
    return sum;
}

int main(){
    int T, v;
    scanf("%d", &T);
    for(int i = 1; i <= T; i++){
        memset(a, 0, sizeof(a));
        scanf("%d", &len);
        for(int j = 1; j <= len; j++){
            scanf("%d", &v);
            update(j, v);
        }
        printf("Case %d:\n", i);
        while(scanf("%s", str)){
            if(str[0] == 'E') break;
            int x, y;
            scanf("%d%d", &x, &y);
            if(str[0] == 'A') update(x, y);
            else
                if(str[0] == 'S') update(x, -y);
                    else printf("%d\n", sum(y)-sum(x-1));
        }
    }
    return 0;
}