第一次写线段树,以杭电一道题为例 张煊的金箍棒(2)
张煊的金箍棒(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.建树其实是先找到叶子节点,赋完值然后向上合并,直合并到最后的题目所给区间

浙公网安备 33010602011771号