张煊的金箍棒(2)
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 313 Accepted Submission(s): 125
Problem Description
张煊的金箍棒升级了!
升级后的金箍棒是由几根相同长度的金属棒连接而成(最开始都是铜棒,从1到N编号);
张煊作为金箍棒的主人,可以对金箍棒施以任意的变换,每次变换操作就是将一段连续的金属棒(从X到Y编号)改为铜棒,银棒或金棒。
金箍棒的总价值计算为N个金属棒的价值总和。其中,每个铜棒价值为1;每个银棒价值为2;每个金棒价值为3。
现在,张煊想知道多次执行操作后的金箍棒总价值。
Input
输入的第一行是测试数据的组数(不超过10个)。
对于每组测试数据,第一行包含一个整数N(1 <= N <= 100000),表示金箍棒有N节金属组成,第二行包含一个整数Q(0 <= Q <= 100,000),表示执行变换的操作次数。
接下来的Q行,每行包含三个整数X,Y,Z(1 <= X <= Y <= N,1 <= Z <= 3),它定义了一个操作:将从X到Y编号的金属棒变换为金属种类Z,其中Z = 1代表铜棒,Z = 2代表银棒,Z = 3代表金棒。
Output
对于每组测试数据,请输出一个数字,表示操作后金箍棒的总价值。
每组数据输出一行。
Sample Input
1
10
2
1 5 2
5 9 3
Sample Output
24
题意:改变一段区间的值,查询一段区间的值
题解:线段树的基本应用,更新与查询区间操作
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
typedef long long ll;
ll val[maxn << 2], lazy[maxn << 2];//开四倍空间存节点
int n, q, x, y, z;
void pushup(int rt) {
val[rt] = val[rt << 1] + val[rt << 1 | 1];
}//上并,建树,更新时都要用到
void pushdown(int rt, int l, int r) {
if (!lazy[rt]) {
return;
}//无标记可以直接退出
int mid = (l + r) >> 1;
val[rt << 1] = lazy[rt] * (mid - l + 1);
val[rt << 1 | 1] = lazy[rt] * (r - mid);
lazy[rt << 1] = lazy[rt];
lazy[rt << 1 | 1] = lazy[rt];
lazy[rt] = 0;//标记下推后清零
}//将懒惰标记下推
void build(int l,int r,int rt) {//构建一颗区间[l,r],根节点为rt的线段树
lazy[rt] = 0;
if (l == r) {//找到叶子节点,值先赋好
val[rt] = 1;
return;
}
int mid = (l + r) >> 1;
build(l, mid, rt << 1);
build(mid + 1, r, rt << 1 | 1);
pushup(rt);//向上合并节点,直到根节点
}
void update(int a,int b,int v,int l,int r,int rt){
if (a > r || b < l)
return;//超出范围了
if (a <= l && b >= r) {//所求完全包含当前,更新当前点,并做好懒惰标记
lazy[rt] = v;
val[rt] = v * (r - l + 1);
return;
}
//若所求区间只是当前区间的一部分,则执行以下代码
pushdown(rt, l, r);//下面的递归更新会用到下面节点,因此这里需要下推标记更新
int mid = (l + r) >> 1;
update(a, b, v, l, mid, rt<<1);
update(a, b, v, mid + 1, r, rt << 1 | 1);
pushup(rt);//找到区间并更新完后,向上并区间
}
int query(int a,int b,int l,int r,int rt) {
if (a > r || b < l)return 0;
if (a <= l && b >= r)return val[rt];
int mid = (l + r) >> 1;
pushdown(rt, l, r);//同理,只要会用到下面的节点就需要更新
return query(a, b, l, mid, rt << 1) + query(a, b, mid + 1, r, rt << 1 | 1);
}
int main(){
int t;
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
cin >> t;
while (t--) {
cin >> n;
build(1,n,1);
cin >> q;
while (q--) {
cin >> x >> y >> z;
update(x, y,z,1,n,1);
}
cout << query(1,n,1,n,1) << endl;
}
}
tips:
1.最好将pushdown和pushup先写前面,逻辑更清晰
2.建树,更新,查询都存在递归的过程
3.建树其实是先找到叶子节点,赋完值然后向上合并,直合并到最后的题目所给区间