线段树的lazy标记进行 区间加 + 区间修改 + HDU - 1698 + Horrible Queries LightOJ - 1164


问题HDU - 1698大意:

给定一排n个铜棍,他们的初始价值为1,现在有q种操作,可以将[x,y]区间内的棍子改为铜棍(价值为1),银棍(价值为2),金棍(价值为3)。问最后所得的所有棍子的价值为多少?

思路:

  • 使用线段树来保存各个区间的总价值。
  • 关于将区间[x,y]进行更新,我们使用lazy数组进行标记,其代表当前区间结点的子区间应该被修改的值(只有在更新和查询需要用到其子区间时才会去更新)
  • 最后对每个问题使用query查询即可,如果查询区间完全覆盖维护区间,则直接返回区间总和;如果不是完全覆盖,就会用到当前区间结点的子区间结点,因此需要使用向下更新pushdown操作。
  • 更新某个区间时,同样的道理,如果完全覆盖,直接更新;否则会用到子区间,就要pushdown之后再进行操作。

AC代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int maxn = 1e5 + 5;
int lazy[maxn << 2], ms[maxn << 2];
int n, q, x, y, z, t;

void pushup(int id){
	ms[id] = ms[id << 1] + ms[id << 1 | 1];
}

void pushdown(int id, int l, int r){
	if(lazy[id]){
		lazy[id << 1] = lazy[id << 1 | 1] = lazy[id];
		int mid = ( l + r ) >> 1;
		ms[id << 1] = (mid - l + 1) * lazy[id];
		ms[id << 1 | 1] = (r - mid) * lazy[id];
		lazy[id] = 0;
	}
}

void update(int id, int l, int r, int x, int y, int v){
	if(x <= l && r <= y){
		lazy[id] = v;
		ms[id] = (r - l + 1) * v;
		return;
	}
	pushdown(id, l, r);
	int mid = (l + r) >> 1;
	if(x <= mid){
		update(id << 1, l, mid, x, y, v);
	}
	if(y > mid){
		update(id << 1 | 1, mid + 1, r, x, y, v);
	}
	pushup(id);
}

int query(int id, int l, int r, int x, int y){
	if(x <= l && r <= y){
		return ms[id];
	}//如果不包含在内,则需要向下更新 
	pushdown(id, l, r);
	int mid = (l + r) >> 1;
	int ans = 0;
	if(x <= mid){
		ans += query(id << 1, l, mid, x, y);
	}
	if(y > mid){
		ans += query(id << 1 | 1, mid + 1, r, x, y);
	}
	return ans;
}

int main(){
	int T = 1;
	scanf("%d",&t); 
	while(t--){
		memset(lazy, 0, sizeof lazy);
		scanf("%d%d", &n, &q);
		update(1, 1, n, 1, n, 1);//将线段树整个区间置位1,即铜棒 
		for(int i = 1;i <= q;i++){
			scanf("%d%d%d", &x, &y, &z);
			update(1, 1, n, x, y, z);
		}
		printf("Case %d: The total value of the hook is %d.\n",  T++, query(1, 1, n, 1, n));
	}
	
	return 0;
}


问题Horrible Queries LightOJ - 1164大意:

给定一个n长度的数组,其初始值都为0。现有q个操作,其中 1 x y v操作代表将区间【x,y】之间的值加上v;而0  x y的操作代表询问[x,y]区间内的和值,根据输入输出完成题目。

思路:

  • 使用一个线段树来维护区间内的和值;
  • 使用lazy数组来标记当前区间结点的子区间应该加上的值,当需要用到子区间的值时,进行pushdown操作对子区间结点的值进行更新;
  • 最后对每个问题使用query查询即可,如果查询区间完全覆盖维护区间,则直接返回区间总和;如果不是完全覆盖,就会用到当前区间结点的子区间结点,因此需要使用向下更新pushdown操作。
  • 更新某个区间时,同样的道理,如果完全覆盖,直接更新;否则会用到子区间,就要pushdown之后再进行操作。

AC代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxn = 1e5 + 5;
int lazy[maxn << 2];
ll ms[maxn << 2]; 
//lazy数组用于存储下标为id的区间结点中,左右儿子区间应该增加的数值 。
//lazy的作用是在有需要的时候,更新当前结点的左右儿子节点的区间和值,当不需要更新的时候就不做多余的更新 

void pushup(int id){
	ms[id] = ms[id << 1] + ms[id << 1 | 1];
}

void pushdown(int id, int l, int r){
	//父节点区间应该加上lazy的值那么子区间也应该加上同样的值,此为下方操作 
	if(lazy[id]){
		lazy[id << 1] += lazy[id]; //让左二子更新其子节点应该增加的值 
		lazy[id << 1 | 1] += lazy[id];
		int mid = (l + r) >> 1;
		ms[id << 1] += lazy[id] * (mid - l + 1);//左子树的和递增 
		ms[id << 1 | 1] += lazy[id] * (r - mid);//右子树的和递增 
		lazy[id] = 0;//增加之后清空,下一次id这个结点区间就不再增加这个值了。 
	}
}

ll query(int id, int l, int r, int x, int y){
	if(x <= l && r <= y){
		return ms[id];
	}
	pushdown(id, l, r);
	//所查询的区间不完全覆盖所维护的区间,所以将所维护的区间向下推,使得当前结点的子节点区间按照因该加上的值更新。 
	int  mid = (l + r) >> 1;
	ll ans = 0;
	if(x <= mid){
		ans += query(id << 1, l, mid, x, y);
	}
	if(y > mid){
		ans += query(id << 1 | 1,mid + 1, r, x, y);
	}
	return ans;
} 

void update(int id, int l, int r, int x, int y, int v){
	if(x <= l && r <= y){
		ms[id] += (r - l + 1) * v;
		lazy[id] += v;
	}else{
		pushdown(id , l, r);//说明当前区间不再查询区间之内,因此要递归到下一层区间结点。
		//所以之前存在于lazy数组中还没有更新的值此时需要被更新。
		int mid = (l + r) >> 1;
		if(x <= mid){
			update(id << 1, l, mid, x, y, v);
		} 
		if(y > mid){
			update(id << 1 | 1, mid + 1, r, x, y, v);
		}
		pushup(id);//向上合并。 
	}
} 

int t, n, m, op, x, y, d;
int main() {
	int cnt = 1;
	scanf("%d", &t);
	while (t--) {
		memset(lazy, 0, sizeof(lazy));
		memset(ms, 0, sizeof(ms));
		printf("Case %d:\n", cnt++);
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= m; i++) {
			scanf("%d%d%d", &op, &x, &y);
			x++, y++;
			if (op == 0) {
				scanf("%d", &d); 
				update(1, 1, n, x, y, d);
			} else {
				printf("%lld\n", query(1, 1, n, x, y));
			}
		}
	} 
	return 0;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值