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

 

posted @ 2021-08-06 21:55  spzjc  阅读(169)  评论(0)    收藏  举报