【牛客数据结构专题班第二节练习题】D 数据结构 题解

题目大意

qn姐姐给你了一个长度为n的序列还有m次操作让你玩,
1 l r 询问区间[l,r]内的元素和
2 l r 询问区间[l,r]内的元素的平方 和
3 l r x 将区间[l,r]内的每一个元素都乘上x
4 l r x 将区间[l,r]内的每一个元素都加上x

输入描述

第一行两个数n,m
接下来一行n个数表示初始序列
接下来m行每行第一个数为操作方法opt,
若opt=1或者opt=2,则之后跟着两个数为l,r
若opt=3或者opt=4,则之后跟着三个数为l,r,x
对于100%的数据 n=10000,m=200000 (注意是等于号)
保证所有询问的答案在long long 范围内

输出描述

对于每一个操作1,2,输出一行表示答案

样例输入

5 6
1 2 3 4 5
1 1 5
2 1 5
3 1 2 1
4 1 3 2
1 1 4
2 2 3

样例输出

15
55
16
41

思路

本题除了平方和需要进行处理,其它操作与洛谷上的线段树模板题完全一致。处理平方和,我们推导出更新平方和的数学公式即可。
t r e e [ f a ] . f p tree[fa].fp tree[fa].fp表示区间内数的一次方之和(即 f i r s t first first p o w pow pow),tree[fa].sp表示区间内数的平方之和(即 s e c o n d second second p o w pow pow)。对于平方的处理,我们有:
结论1:区间 [ m , n ] [m,n] [m,n]内所有数乘 p p p,等价于对 t r e e [ f a ] . s p tree[fa].sp tree[fa].sp p 2 p^2 p2
证明: ∑ i = m n ( p × a i ) 2 = p 2 × ( ∑ i = m n a i 2 ) \sum_{i=m}^{n}(p×a_i)^2=p^2×(\sum_{i=m}^{n}a_i^2) i=mn(p×ai)2=p2×(i=mnai2),证毕。
结论2:区间 [ m , n ] [m,n] [m,n]内所有数加 p p p,等价于对 t r e e [ f a ] . s p tree[fa].sp tree[fa].sp 2 × p × t r e e [ f a ] . f p + c l e n × p 2 2×p×tree[fa].fp+clen×p^2 2×p×tree[fa].fp+clen×p2
证明: ∑ i = m n ( a i + p ) 2 = ∑ i = m n ( a i 2 + 2 × p × a i + p 2 ) \sum_{i=m}^{n}(a_i+p)^2=\sum_{i=m}^{n}(a_i^2+2×p×a_i+p^2) i=mn(ai+p)2=i=mn(ai2+2×p×ai+p2)
= ( ∑ i = m n a i 2 ) + 2 × p × ( ∑ i = m n a i ) + ( n − m + 1 ) × p 2 =(\sum_{i=m}^{n}a_i^2)+2×p×(\sum_{i=m}^{n}a_i)+(n-m+1)×p^2 =(i=mnai2)+2×p×(i=mnai)+(nm+1)×p2
= ( ∑ i = m n a i 2 ) + ( 2 × p × t r e e [ f a ] . f p + c l e n × p 2 ) =(\sum_{i=m}^{n}a_i^2)+(2×p×tree[fa].fp+clen×p^2) =(i=mnai2)+(2×p×tree[fa].fp+clen×p2),证毕。

考点

带懒惰标记线段树
简单数学

AC代码

#include<iostream>
#include<cstring>
#include<math.h>
#define int long long
#define lson fa<<1
#define llen (len-(len>>1))
#define rson fa<<1|1
#define rlen (len>>1)
#define mid ((cL+cR)>>1)
#define clen (cR-cL+1)
using namespace std;
const int maxn = 1e4 + 5;
int opt;
class Segment_Tree {
public:
	struct Node {
		int fp, sp;
	}tree[maxn << 2];
	int mark_add[maxn << 2], mark_mul[maxn << 2], a[maxn];
	void init() {
		memset(mark_add, 0, sizeof(mark_add));
		for (int i = 1; i < (maxn << 2); i++)mark_mul[i] = 1;
	}
	void built(int cL, int cR, int fa) {
		if (cL == cR) {
			tree[fa].fp = a[cL]; 
			tree[fa].sp = pow(a[cL], 2);
			return; 
		}
		built(cL, mid, lson);
		built(mid + 1, cR, rson);
		tree[fa].fp = tree[lson].fp + tree[rson].fp;
		tree[fa].sp = tree[lson].sp + tree[rson].sp;
	}
	inline void push_down(int len, int fa) {
		mark_add[lson] += mark_add[fa];
		mark_mul[lson] *= mark_mul[fa];
		tree[lson].sp = tree[lson].sp * pow(mark_mul[fa], 2) + 2 * mark_add[fa] * tree[lson].fp + pow(mark_add[fa], 2) * llen;
		tree[lson].fp = tree[lson].fp * mark_mul[fa] + mark_add[fa] * llen;
		mark_add[rson] += mark_add[fa];
		mark_mul[rson] *= mark_mul[fa];
		tree[rson].sp = tree[rson].sp * pow(mark_mul[fa], 2) + 2 * mark_add[fa] * tree[rson].fp + pow(mark_add[fa], 2) * rlen;
		tree[rson].fp = tree[rson].fp * mark_mul[fa] + mark_add[fa] * rlen;
		mark_add[fa] = 0;
		mark_mul[fa] = 1;
	}
	void update(int sL, int sR, int cL, int cR, int fa, int x) {
		if (cR<sL || cL>sR) { return; }
		else if (cL >= sL&&cR <= sR) {
			if (opt == 3) {
				tree[fa].fp *= x;
				tree[fa].sp *= pow(x, 2);
			}
			if (opt == 4) {
				tree[fa].sp = tree[fa].sp + 2 * x*tree[fa].fp + pow(x, 2)*clen;
				tree[fa].fp += x*clen;
			}
			if (cL < cR) {
				if (opt == 3) {
					mark_add[fa] *= x;
					mark_mul[fa] *= x;
				}
				if (opt == 4)mark_add[fa] += x;
			}
		}
		else {
			push_down(clen, fa);
			update(sL, sR, cL, mid, lson, x);
			update(sL, sR, mid + 1, cR, rson, x);
			tree[fa].fp = tree[lson].fp + tree[rson].fp;
			tree[fa].sp = tree[lson].sp + tree[rson].sp;
		}
	}
	int query(int sL, int sR, int cL, int cR, int fa) {
		if (cR<sL || cL>sR) { return 0; }
		else if (cL >= sL&&cR <= sR) {
			if (opt == 1)return tree[fa].fp;
			if (opt == 2)return tree[fa].sp;
		}
		else {
			push_down(clen, fa);
			return query(sL, sR, cL, mid, lson) + query(sL, sR, mid + 1, cR, rson);
		}
	}
};
signed main()
{
	Segment_Tree seg;
	seg.init();
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)cin >> seg.a[i];
	seg.built(1, n, 1);
	int l, r, x, ans;
	for (int i = 1; i <= m; i++) {
		while (cin >> opt) {
			if (opt == 1 || opt == 2) {
				cin >> l >> r;
				ans = seg.query(l, r, 1, n, 1);
				cout << ans << endl;
			}
			if (opt == 3 || opt == 4) {
				cin >> l >> r >> x;
				seg.update(l, r, 1, n, 1, x);
			}
		}
	}
	return 0;
}

心得

本题考察了懒惰标记的应用,将公式推导与线段树的懒惰标记结合起来,非常容易让人搞不清懒惰标记添加和下放的具体细节。更新平方和要用到线段上数原本的一次方和,因此要先更新平方和,再更新一次方和。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

keguaiguai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值