第一次写线段树,以杭电一道题为例 张煊的金箍棒(2)

11 篇文章 0 订阅

张煊的金箍棒(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.建树其实是先找到叶子节点,赋完值然后向上合并,直合并到最后的题目所给区间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值