问题描述
有n个格子,从左到右放成一排,编号为1-n。
共有m次操作,有3种操作类型:
1.修改一个格子的权值,
2.求连续一段格子权值和,
3.求连续一段格子的最大值。
对于每个2、3操作输出你所求出的结果。
输入格式
第一行2个整数n,m。
接下来一行n个整数表示n个格子的初始权值。
接下来m行,每行3个整数p,x,y,p表示操作类型,
p=1时表示修改格子x的权值为y,
p=2时表示求区间[x,y]内格子权值和,
p=3时表示求区间[x,y]内格子最大的权值。
输出格式
有若干行,行数等于p=2或3的操作总数。
每行1个整数,对应了每个p=2或3操作的结果。
样例输入
4 3
1 2 3 4
2 1 3
1 4 3
3 1 4
样例输出
6
3
数据规模与约定
对于20%的数据n <= 100,m <= 200。
对于50%的数据n <= 5000,m <= 5000。
对于100%的数据1 <= n <= 100000,m <= 100000,0 <= 格子权值 <= 10000。
解题思路
由数据规模得,使用int数据类型即可。
注意,区间计数是从1开始的。
按照一般的思路提交,可是超时,刚好看到题目锦囊提示使用线段树。
线段树(Segment Tree)是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
对于线段树中的每一个非叶子节点[a,b],它的左子树表示的区间为[a,(a+b)/2],右子树表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树。叶节点数目为N,即整个线段区间的长度。
简单的说,顾名思义,就是将一段线段(如:1-10),不断的划分,形成一棵树。
在这里,使用数组存放满二叉树,那数组的大小是多少呢?
在构建和利用线段树的过程中,充分使用了分治的思想。
题目中说,“p=1时表示修改格子x的权值为y”,这个x代表的是格子的位置。
创建的是1到n的线段树,其实每个结点的权重是不需要额外存储的,只需知道权重的和与最大值。叶子结点的sum和max即为权重。
注意,数组从1开始存储,利于二叉树结点位置的确定。
找了好几个小时的错误,原来是在找最大值的地方,把right写成了left,悲哀!忽然有种高中做数学题的悲哀。
代码实现
数组或容器
只能满足前50%的数据。
注意,在case语句中最好不要定义并初始化变量,常引起错误。
case后加大括号,可定义局部变量。
- #include <iostream>
- #include <vector>
- #include <algorithm>
- using namespace std;
- int main(void) {
- int n, m;
- cin >> n >> m;
- vector<int> v;
- while(n--) {
- int tmp;
- cin >> tmp;
- v.push_back(tmp);
- }
- while(m--) {
- int p, x, y;
- cin >> p >> x >> y;
- int sum = 0;
- int max = 0;
- switch(p) {
- case 1 :
- v[x-1] = y;
- break;
- case 2 :
- for(int i = x; i <= y; i++) {
- sum += v[i - 1];
- }
- cout << sum << endl;
- break;
- case 3 :
- for(int i = x; i <= y; i++) {
- if(v[i - 1] > max) {
- max = v[i - 1];
- }
- }
- cout << max << endl;
- break;
- default :
- break;
- }
- }
- return 0;
- }
线段树
这里使用数组存放线段树,其实和线段树相关的操作只涉及到4种:
- 构建线段树
- 改变线段树中某个结点的值
- 求某区间的结点权重的和
- 求某区间的结点权重的最大值
只是存放线段树的数组的大小还没搞清楚,从coolnote下载的测试数据表明:
当n为100000时,最后一个不为0的数组索引为262142。
给定【1,n】,建立线性二叉树,其层数是确定的,根据二分原理,其层数为m=上取整(log(n))+1 , 那么数组上界为这棵满二叉树所有节点个数: 2^m-1=2^(上取整(log(n))+1)-1
举个例子,n=5,树层数为m=4,上界为2^4-1=15
n=6,树层数为m=4,上界为2^4-1=15
n=100000,树层数为m=18,上界为2^18-1=262143
- #include <iostream>
- #include <cstring>
- using namespace std;
- typedef struct SNode {
- int max, sum;
- int left, right;
- } SNode;
- SNode t[300000];
- int two_max(int a, int b) {
- return a > b ? a : b;
- }
- void build(int pos, int l, int r) {
- t[pos].left = l;
- t[pos].right = r;
- t[pos].max = 0;
- t[pos].sum = 0;
- if(l == r) {
- return;
- }
- build(pos * 2, l, (l + r) / 2);
- build(pos * 2 + 1, (l + r) / 2 + 1, r);
- }
- void change(int pos, int v, int w) {
- if(t[pos].left == v && t[pos].right == v) {
- t[pos].sum = w;
- t[pos].max = w;
- return;
- }
- int middle = (t[pos].left + t[pos].right) / 2;
- if(v <= middle) {
- change(pos * 2, v, w);
- } else {
- change(pos * 2 + 1, v, w);
- }
- t[pos].sum = t[pos * 2].sum + t[pos * 2 + 1].sum;
- t[pos].max = two_max(t[pos * 2].max, t[pos * 2 + 1].max);
- }
- int get_sum(int pos, int l, int r) {
- if(t[pos].left == l && t[pos].right == r) {
- return t[pos].sum;
- }
- int middle = (t[pos].left + t[pos].right) / 2;
- if(r <= middle) {
- return get_sum(pos * 2, l, r);
- }
- if(l > middle) {
- return get_sum(pos * 2 + 1, l, r);
- }
- return get_sum(pos * 2, l, middle) + get_sum(pos * 2 + 1, middle + 1, r);
- }
- int get_max(int pos, int l, int r) {
- if(t[pos].left == l && t[pos].right == r) {
- return t[pos].max;
- }
- int middle = (t[pos].left + t[pos].right) / 2;
- if(r <= middle) {
- return get_max(pos * 2, l, r);
- }
- if(l > middle) {
- return get_max(pos * 2 + 1, l, r);
- }
- return two_max(get_max(pos * 2, l, middle), get_max(pos * 2 + 1, middle + 1, r));
- }
- int main(void) {
- memset(t, 0, sizeof(t));
- int n, m;
- cin >> n >> m;
- build(1, 1, n);
- for(int i = 1; i <= n; i++) {
- int tmp;
- cin >> tmp;
- change(1, i, tmp);
- }
- while(m--) {
- int p, x, y;
- cin >> p >> x >> y;
- switch(p) {
- case 1 :
- change(1, x, y);
- break;
- case 2 :
- cout << get_sum(1, x, y) << endl;
- break;
- case 3 :
- cout << get_max(1, x, y) << endl;
- break;
- }
- }
- return 0;
- }