ACM寒假集训#5---线段树

##第五天-----线段树

线段树示例:
在这里插入图片描述

建树代码:

const int N=50010; 
int arr[N];//原数组
int tree[4*N];//树-数组
void bulid_tree(int arr[],int tree[],int node,int start,int end) {
     //node是当前的树枝编号就是图中红色的
     //start和end是数组的左端点和右端点的下标
    //start和end就是图中中括号里的东西
	if(start==end)
		tree[node]=arr[start];
       //递归出口,当两个端点相等时,也就是无法在分割时
       //树上的值就是数组本身的值,也可以写成tree[node]=arr[end]
	else {
		int mid=start+end>>1;
		//取两个端点的中间值,向下取整
		int left_node=2*node+1;
		int right_node=2*node+2;
		//分支,每个树枝都有两个分支
        //由上面的红色序号易得,比如2的两个分支是5和6
        
		bulid_tree(arr,tree,left_node,start,mid);
		bulid_tree(arr,tree,right_node,mid+1,end);
		//递归,建立下面两个树枝
		tree[node]=tree[left_node]+tree[right_node];//取值
	}
}

一般输入数组之后,调用这个函数把树先建起来:

for(int i=1;i<=n;i++)//从1开始到n,用0到n-1比较麻烦
scanf("%d",&a[i]);
build_tree(arr,tree,0,1,n)//一开始node=0

Update:

void update_tree(int arr[], int tree[], int node, int start,int end,int idx,int val) {
  //idx是要改数字的下标,val是要改成的数字
	if(start==end) {
		arr[idx]=val;
		tree[node]=val;
	} 
	//同样的是递归出口,就是树上的值不是某个范围而是本身的值时
	//注意要把数组本身改掉,同时树上的值也改掉
	else {
		int mid=(start+end)/2;
		int left_node=2*node+1;
		int right_node=2*node+2;
		if(idx>=start&&idx<=mid) {
			update_tree(arr,tree,left_node,start,mid,idx,val);
		} else {
			update_tree(arr,tree,right_node,mid+1,end,idx,val);
		}
		//if-else是判断是往左边改还是往右边改
		tree[node]=tree[left_node]+tree[right_node];
	}
}

在这里插入图片描述

/*比如要把4号元素9改成6,分步看:
首先tree[0]--->33
2=mid<4<end=5
所以往左改
tree[2]--->24
4=mid
所以往右改
tree[5]--->13
接下来4>mid=3,所以往右下走
最后传入函数start=end
所以
tree[12]--->6
arr[start]=6*/

Query:

int query_tree(int arr[],int tree[],int node,int start,int end,int L,int R) {
    //L,R是要取和的范围
	if(R<start||L>end) {
		return 0;
	}
	//如果区间没有交集直接返回0
	 else if(start==end) {
		return tree[node];
	} 
	//单个值直接返回
	else if(L<=start&&end<=R) {
		return tree[node];
	} 
	//如果L,R被包含在start,end的区间里面,直接返回
	else {
	//既有不在区间内的,又有在区间内的
		int mid=(start+end)/2;
		int left_node=2*node+1;
		int right_node=2*node+2;
		int sum_left=query_tree(arr,tree,left_node,start,mid,L,R);
		int sum_right=query_tree(arr,tree,right_node,mid+1,end,L,R);
		return sum_left+sum_right;
		//利用递归求出左右两边的和
	}
}

例题:
Input:
第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令
Output:
对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。
Sample Input:
1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End
/
Sample Output:
Case 1:
6
33
59

AC代码:

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
const int N=50010;
using namespace std;
void build_tree(int arr[],int tree[],int node,int start,int end) {
	if(start==end) {
		tree[node]=arr[start];
	} else {
		int mid=(start+end)/2;
		int left_node=2*node+1;
		int right_node=2*node+2;
		build_tree(arr,tree,left_node,start,mid);
		build_tree(arr,tree,right_node,mid+1,end);
		tree[node]=tree[left_node]+tree[right_node];
	}
}

void update_tree(int arr[], int tree[], int node, int start,int end,int idx,int val) {
	if(start==end) {
		arr[idx]+=val;
		tree[node]+=val;
	} else {
		int mid=(start+end)/2;
		int left_node=2*node+1;
		int right_node=2*node+2;
		if(idx>=start&&idx<=mid) {
			update_tree(arr,tree,left_node,start,mid,idx,val);
		} else {
			update_tree(arr,tree,right_node,mid+1,end,idx,val);
		}
		tree[node]=tree[left_node]+tree[right_node];
	}
}

int query_tree(int arr[],int tree[],int node,int start,int end,int L,int R) {
	if(R<start||L>end) {
		return 0;
	} else if(start==end) {
		return tree[node];
	} 
	else if(L<=start&&end<=R) {
		return tree[node];
	}
	else {
		int mid=(start+end)/2;
		int left_node=2*node+1;
		int right_node=2*node+2;
		int sum_left=query_tree(arr,tree,left_node,start,mid,L,R);
		int sum_right=query_tree(arr,tree,right_node,mid+1,end,L,R);
		return sum_left+sum_right;
	}
}


int main(void) {
	int t,n,j=1;
	scanf("%d",&t);
	while(t--) {
		int k=0;
		scanf("%d",&n);
		int arr[N],sum[40000];
		int tree[4*N]= {0};
		for(int i=1; i<=n; i++)
			scanf("%d",&arr[i]);
		build_tree(arr,tree,0,1,n);
		char op[10];
		while(scanf("%s",op)) {
			int x,y;
			if(!strcmp(op,"End"))
				break;
			else if(!strcmp(op,"Add")) {
				scanf("%d %d",&x,&y);
				update_tree(arr,tree,0,1,n,x,y);
			} else if(!strcmp(op,"Sub")) {
				scanf("%d %d",&x,&y);
				int z=-y;
				update_tree(arr,tree,0,1,n,x,z);
			} else {
				scanf("%d %d",&x,&y);
				sum[k++]=query_tree(arr,tree,0,1,n,x,y);
			}
		}
		printf("Case %d:\n",j);
		for(int i=0; i<k; i++)
			printf("%d\n",sum[i]);
		j++;
	}
	return 0;
}

呜呜,好难,溜了!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值