问题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;
}