牛客竞赛数据结构专题班 线段树练习题

这篇博客详细解析了牛客竞赛中关于树状数组和线段树的练习题,包括区间最小值查询、区间和、平方和的维护以及区间乘法操作。通过实例展示了如何使用树状数组和线段树解决这些复杂问题,涉及到了懒惰标记、区间加法、乘法和查询的高效实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

牛客竞赛数据结构专题班树状数组、线段树练习题

zngg的专题题解

树状数组单开一个博客


[NOIP2012]借教室

二分题解
二分答案,用差分+前缀和check

题意:
m m m 次操作,每次区间 − d -d d
每次操作前判断最小值是否 > = d >=d >=d

区间修改,维护区间最小值
code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 5;
const int inf = 0x3f3f3f3f;
ll tree[MAXN << 2], mark[MAXN << 2], n, m, A[MAXN];
void push_down(int p, int len)
{
	if(!mark[p]) return;
    tree[p << 1] -= mark[p];
    mark[p << 1] += mark[p];
    tree[p << 1 | 1] -= mark[p];
    mark[p << 1 | 1] += mark[p];
    mark[p] = 0;
}
void build(int p = 1, int cl = 1, int cr = n)
// 函数定义参数的时候赋值,如果没传参数,就是赋予的值,否则就是传递的值
{
    if (cl == cr) { tree[p] = A[cl]; return; }
    int mid = (cl + cr) >> 1;
    build(p << 1, cl, mid);
    build(p << 1 | 1, mid + 1, cr);
    tree[p] = min(tree[p << 1], tree[p << 1 | 1]);
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n)
{
    if (cl >= l && cr <= r) return tree[p];
    push_down(p, cr - cl + 1);
    ll mid = (cl + cr) >> 1, ans = inf;
    if (mid >= l) ans = min(query(l, r, p << 1, cl, mid), ans);
    if (mid < r) ans = min(query(l, r, p << 1 | 1, mid + 1, cr), ans);
    return ans;
}
void update(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
    if (cl >= l && cr <= r) { tree[p] -= d, mark[p] += d; return; }
    // 只有整个区间被修改的节点才会有懒惰标记
    push_down(p, cr - cl + 1);
    int mid = (cl + cr) >> 1;
    if (mid >= l) update(l, r, d, p << 1, cl, mid);
    if (mid < r) update(l, r, d, p << 1 | 1, mid + 1, cr);
    tree[p] = min(tree[p << 1], tree[p << 1 | 1]);
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> A[i];
    build();
    int ans = 0;
    for(int i = 1; i <= m; ++i)
    {
		int d, l, r;
        cin >> d >> l >> r;
    	if(query(l, r) >= d){
    		update(l, r, d);
		}
    	else {
    		ans = i;break;
		}
    }
    if(ans) cout << -1 << endl;
    cout << ans;
    return 0;
}

数据结构
题意:
四个操作

  1. 查询区间和
  2. 查询区间平方和
  3. 区间乘 x x x
  4. 区间加 x x x

思路:
这也是一道维护区间加分和乘法的题目,但是多了一查询方式,查询区间所有数的平方的和。
区间和容易维护,那区间平方和和立方和呢?
维护区间平方和的题目
难度比这个小一点,没有区间乘的更新

区间平方和的维护:

s u m 1 = a 1 + a 2 + a 3 + . . . + a n s u m 2 = a 1 2 + a 2 2 + a 3 2 + . . . + a n 2 sum_1=a_1+a_2+a_3+...+a_n\\ sum_2=a_1^2+a_2^2+a_3^2+...+a_n^2\\ sum1=a1+a2+a3+...+ansum2=a12+a22+a32+...+an2

区间加 x x x
( a 1 + x ) 2 + ( a 2 + x ) 2 + ( a 3 + x ) 2 + . . . + ( a n + x ) 2 = a 1 2 + 2 a 1 x + x 2 + a 2 2 + 2 a 2 x + x 2 + a 3 + 2 a 3 x + x 3 + . . . + a n 2 + 2 a n x + x 2 = ( a 1 2 + a 2 2 + a 3 2 + . . . + a n 2 ) + 2 x ( a 1 + a 2 + a 3 + . . . + a n ) + n x 2 = s u m 2 + 2 x ∗ s u m 1 + n x 2 (a_1+x)^2+(a_2+x)^2+(a_3+x)^2+...+(a_n+x)^2\\ = a_1^2+2a_1x+x^2+a_2^2+2a_2x+x^2+a^3+2a_3x+x^3+...+a_n^2+2a_nx+x^2\\ = (a_1^2+a_2^2+a_3^2+...+a_n^2)+2x(a_1+a_2+a_3+...+a_n)+nx^2\\ =sum_2+2x*sum_1+nx^2 (a1+x)2+(a2+x)2+(a3+x)2+...+(an+x)2=a12+2a1x+x2+a22+2a2x+x2+a3+2a3x+x3+...+an2+2anx+x2=(a12+a22+a32+...+an2)+2x(a1+a2+a3+...+an)+nx2=sum2+2xsum1+nx2
区间乘 y y y
( y a 1 ) 2 + ( y a 2 ) 2 + ( y a 3 ) 2 + . . . + ( y a n ) 2 = y 2 ( a 1 2 + a 2 2 + a 3 2 + . . . + a n 2 ) (ya_1)^2+(ya_2)^2+(ya_3)^2+...+(ya_n)^2\\ =y^2(a_1^2+a_2^2+a_3^2+...+a_n^2) (ya1)2+(ya2)2+(ya3)2+...+(yan)2=y2(a12+a22+a32+...+an2)

区间乘 y + x y+x y+x
( y a 1 + x ) 2 + ( y a 2 + x ) 2 + ( y a 3 + x ) 2 + . . . + ( y a n + x ) 2 = y 2 a 1 2 + 2 a 1 x y + x 2 + y 2 a 2 2 + 2 a 2 x y + x 2 + y 2 a 3 + 2 a 3 x y + x 3 + . . . + y 2 a n 2 + 2 a n x y + x 2 = y 2 ( a 1 2 + a 2 2 + a 3 2 + . . . + a n 2 ) + 2 x y ( a 1 + a 2 + a 3 + . . . + a n ) + n x 2 = y 2 s u m 2 + 2 x y ∗ s u m 1 + n x 2 (ya_1+x)^2+(ya_2+x)^2+(ya_3+x)^2+...+(ya_n+x)^2\\ = y^2a_1^2+2a_1xy+x^2+y^2a_2^2+2a_2xy+x^2+y^2a^3+2a_3xy+x^3+...+y^2a_n^2+2a_nxy+x^2\\ = y^2(a_1^2+a_2^2+a_3^2+...+a_n^2)+2xy(a_1+a_2+a_3+...+a_n)+nx^2\\ =y^2sum_2+2xy*sum_1+nx^2 (ya1+x)2+(ya2+x)2+(ya3+x)2+...+(yan+x)2=y2a12+2a1xy+x2+y2a22+2a2xy+x2+y2a3+2a3xy+x3+...+y2an2+2anxy+x2=y2(a12+a22+a32+...+an2)+2xy(a1+a2+a3+...+an)+nx2=y2sum2+2xysum1+nx2

注意 n n n 是区间长度
然后就可以愉快的写 u p d a t e update update p u s h d o w n pushdown pushdown 函数啦~

先乘后加的原因

(这个题就是洛谷线段树模板二和上边维护区间平方和的结合体
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
using namespace std;
const int maxn = 1e5 + 9;
ll n, m;
ll a[maxn];
struct node
{
	ll sum, add, mul;
}t1[maxn << 2], t2[maxn << 2];

inline void pushdown(int p, int len)
{
	//if(t1[p].mark != t2[p].mark) return;  //   测试一下,显然 t1和t2 的 mark是完全一样的,不一样会因为这句wa掉 
	ll x = t2[p].add, y = t2[p].mul;// 写成t1也一样
	// 先维护平方和,再维护和!!
	// 区间平方和 
	t2[p << 1].add = t2[p << 1].add * y + x;
	t2[p << 1].mul = t2[p << 1].mul * y;
	t2[p << 1].sum = t2[p << 1].sum * y * y + 2 * x * y * t1[p << 1].sum + (len - len / 2) * x * x;
	
	t2[p << 1 | 1].add = t2[p << 1 | 1].add * y + x;
	t2[p << 1 | 1].mul = t2[p << 1 | 1].mul * y;
	t2[p << 1 | 1].sum = t2[p << 1 | 1].sum * y * y + 2 * x * y * t1[p << 1 | 1].sum + (len / 2) * x * x;
	// 区间和 
	t1[p << 1].add = t1[p << 1].add * y + x;
	t1[p << 1].mul = t1[p << 1].mul * y;
	t1[p << 1].sum = t1[p << 1].sum * y + (len - len / 2) * x; 
	
	t1[p << 1 | 1].add = t1[p << 1 | 1].add * y + x;
	t1[p << 1 | 1].mul = t1[p << 1 | 1].mul * y;
	t1[p << 1 | 1].sum = t1[p << 1 | 1].sum * y + (len / 2) * x; 
	
	t1[p].add = t2[p].add = 0;
	t1[p].mul = t2[p].mul = 1;
}
inline void build(int p = 1, int cl = 1, int cr = n)
{
	t1[p].add = t2[p].add = 0;t1[p].mul = t2[p].mul = 1;
	if(cl == cr){
		t1[p].sum = a[cl]; t2[p].sum= a[cl] * a[cl];return;
	}
	int mid = (cl + cr) >> 1;
	build(p << 1, cl, mid);
	build(p << 1 | 1, mid + 1, cr);
	t1[p].sum = t1[p << 1].sum + t1[p << 1 | 1].sum;
	t2[p].sum = t2[p << 1].sum + t2[p << 1 | 1].sum;
}
inline ll query(node tree[], int l, int r, int p = 1, int cl = 1, int cr = n)
{
	if(cl >= l && cr <= r) return tree[p].sum;
	int mid = (cl + cr) >> 1;
	if(tree[p].add || tree[p].mul != 1) pushdown(p, cr - cl + 1);
	ll ans = 0;
	if(mid >= l) ans += query(tree, l, r, p << 1, cl, mid);
	if(mid < r) ans += query(tree, l, r, p << 1 | 1, mid + 1, cr);
	//t1[p].sum = t1[p << 1].sum + t1[p << 1 | 1].sum;
	//t2[p].sum = t2[p << 1].sum + t2[p << 1 | 1].sum;
	// 不修改值,不用 pushup 
	return ans;
}
inline void add(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
	if(cl >= l && cr <= r){
		t1[p].add += d; t2[p].add += d;
		t2[p].sum += t1[p].sum * (d * 2) + (cr - cl + 1) * (d * d);
		t1[p].sum += (cr - cl + 1) * d;
		return;
	}
	if(t1[p].add || t1[p].mul != 1) pushdown(p, cr - cl + 1);
	int mid = (cl + cr) >> 1;
	if(mid >= l) add(l, r, d, p << 1, cl, mid);
	if(mid < r) add(l, r, d, p << 1 | 1, mid + 1, cr);
	t1[p].sum = t1[p << 1].sum + t1[p << 1 | 1].sum;
	t2[p].sum = t2[p << 1].sum + t2[p << 1 | 1].sum;
}
inline void mul(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
	if(cl >= l && cr <= r){
		t1[p].mul *= d; t2[p].mul *= d;
		t1[p].add *= d; t2[p].add *= d;
		t1[p].sum *= d; t2[p].sum *= d * d;
		return;
	}
	if(t1[p].add || t1[p].mul != 1) pushdown(p, cr - cl + 1);
	int mid = (cl + cr) >> 1;
	if(mid >= l) mul(l, r, d, p << 1, cl, mid);
	if(mid < r) mul(l, r, d, p << 1 | 1, mid + 1, cr);
	t1[p].sum = t1[p << 1].sum + t1[p << 1 | 1].sum;
	t2[p].sum = t2[p << 1].sum + t2[p << 1 | 1].sum;
}
void work()
{
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) cin >> a[i];
	build();
	while(m--)
	{
		int op, l, r;
		cin >> op >> l >> r;
		if(op == 1){
			cout << query(t1, l, r) << endl;
		}
		else if(op == 2){
			cout << query(t2, l, r) << endl;
		}
		else if(op == 3) {
			int k;cin >> k;mul(l, r, k);
		}
		else {
			int k;cin >> k;add(l, r, k);
		}
	}
}
int main()
{
	ios::sync_with_stdio(0);
	work();
	return 0;
}

线段树
题意:
三个操作

  1. 区间加 x x x
  2. 区间乘 x x x
  3. 查询区间所有两个数的乘积之和

思路:
如何维护区间任意两个数的乘积
∑ i = l r ∑ j = i + 1 r a i a j ⟹ ∑ i = l r a i ∑ j = i + 1 r a j ⟹ ∑ i = l r a i ∑ j = l r a j − ∑ i = l r a i 2 2 ⟹ ( ∑ i = l r a i ) 2 − ∑ i = l r a i 2 2 \sum_{i=l}^{r}\sum_{j=i+1}^{r}a_ia_j\Longrightarrow \sum_{i=l}^{r}a_i\sum_{j=i+1}^{r}a_j\Longrightarrow \frac{\sum_{i=l}^{r}a_i\sum_{j=l}^{r}a_j-\sum_{i=l}^{r}a_i^2}{2}\Longrightarrow\frac{(\sum_{i=l}^{r}a_i)^2-\sum_{i=l}^{r}a_i^2}{2} i=lrj=i+1raiaji=lraij=i+1raj2i=lraij=lraji=lrai22(i=lrai)2i=lrai2
(区间平方和 − - 区间和)/ 2 得到的显然是区间任意两个数的乘积,因此我们维护这两个和即可

和上一个题的差不多
区间加法乘法操作,维护区间和区间平方和

code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
using namespace std;
const int maxn = 1e5 + 9;
ll mod;
ll n, m;
ll a[maxn];
struct node
{
	ll sum, add, mul;
}t1[maxn << 2], t2[maxn << 2];

inline void pushdown(int p, int len)
{
	//if(t1[p].mark != t2[p].mark) return;  //   测试一下,显然 t1和t2 的 mark是完全一样的,不一样会因为这句wa掉 
	ll x = t2[p].add % mod, y = t2[p].mul % mod;// 写成t1也一样
	// 先维护平方和,再维护和!!
	// 区间平方和 
	t2[p << 1].add = (t2[p << 1].add * y % mod + x) % mod;
	t2[p << 1].mul = t2[p << 1].mul * y % mod;
	t2[p << 1].sum = ((t2[p << 1].sum * (y * y % mod) % mod + 2 * ((x * y % mod) * t1[p << 1].sum % mod) % mod) % mod + (len - len / 2) * (x * x % mod) % mod) % mod;
	
	t2[p << 1 | 1].add = (t2[p << 1 | 1].add * y % mod + x) % mod;
	t2[p << 1 | 1].mul = t2[p << 1 | 1].mul * y % mod;
	t2[p << 1 | 1].sum = ((t2[p << 1 | 1].sum * (y * y % mod) % mod + 2 * ((x * y % mod) * t1[p << 1 | 1].sum % mod) % mod) % mod + (len / 2) * (x * x % mod) % mod) % mod;
	// 区间和 
	t1[p << 1].add = (t1[p << 1].add * y % mod + x) % mod;
	t1[p << 1].mul = t1[p << 1].mul * y % mod;
	t1[p << 1].sum = (t1[p << 1].sum * y % mod + (len - len / 2) * x % mod) % mod; 
	
	t1[p << 1 | 1].add = (t1[p << 1 | 1].add * y % mod + x) % mod;
	t1[p << 1 | 1].mul = t1[p << 1 | 1].mul * y % mod;
	t1[p << 1 | 1].sum = (t1[p << 1 | 1].sum * y % mod + (len / 2) * x % mod) % mod; 
	
	t1[p].add = t2[p].add = 0;
	t1[p].mul = t2[p].mul = 1;
}
inline void build(int p = 1, int cl = 1, int cr = n)
{
	t1[p].add = t2[p].add = 0;t1[p].mul = t2[p].mul = 1;
	if(cl == cr){
		t1[p].sum = a[cl] % mod; t2[p].sum = a[cl] * a[cl] % mod;return;
	}
	int mid = (cl + cr) >> 1;
	build(p << 1, cl, mid);
	build(p << 1 | 1, mid + 1, cr);
	t1[p].sum = (t1[p << 1].sum + t1[p << 1 | 1].sum) % mod;
	t2[p].sum = (t2[p << 1].sum + t2[p << 1 | 1].sum) % mod;
}
inline ll query(node tree[], int l, int r, int p = 1, int cl = 1, int cr = n)
{
	if(cl >= l && cr <= r) return tree[p].sum % mod;
	int mid = (cl + cr) >> 1;
	if(tree[p].add || tree[p].mul != 1) pushdown(p, cr - cl + 1);
	ll ans = 0;
	if(mid >= l) ans += query(tree, l, r, p << 1, cl, mid), ans %= mod;
	if(mid < r) ans += query(tree, l, r, p << 1 | 1, mid + 1, cr), ans %= mod;
	//t1[p].sum = t1[p << 1].sum + t1[p << 1 | 1].sum;
	//t2[p].sum = t2[p << 1].sum + t2[p << 1 | 1].sum;
	// 不修改值,不用 pushup 
	return ans;
}
inline void add(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
	if(cl >= l && cr <= r){
		t1[p].add += d; t2[p].add += d; t1[p].add %= mod; t2[p].add %= mod;
		t2[p].sum += t1[p].sum * (d * 2) % mod + (cr - cl + 1) * (d * d % mod) % mod; t2[p].sum %= mod;
		t1[p].sum += (cr - cl + 1) * d; t1[p].sum %= mod;
		return;
	}
	if(t1[p].add || t1[p].mul != 1) pushdown(p, cr - cl + 1);
	int mid = (cl + cr) >> 1;
	if(mid >= l) add(l, r, d, p << 1, cl, mid);
	if(mid < r) add(l, r, d, p << 1 | 1, mid + 1, cr);
	t1[p].sum = (t1[p << 1].sum + t1[p << 1 | 1].sum) % mod;
	t2[p].sum = (t2[p << 1].sum + t2[p << 1 | 1].sum) % mod;
}
inline void mul(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
	if(cl >= l && cr <= r){
		t1[p].mul *= d; t2[p].mul *= d; t1[p].mul %= mod; t2[p].mul %= mod;
		t1[p].add *= d; t2[p].add *= d; t1[p].add %= mod; t2[p].add %= mod;
		t1[p].sum *= d; t2[p].sum *= d * d % mod; t1[p].sum %= mod; t2[p].sum %= mod;
		return;
	}
	if(t1[p].add || t1[p].mul != 1) pushdown(p, cr - cl + 1);
	int mid = (cl + cr) >> 1;
	if(mid >= l) mul(l, r, d, p << 1, cl, mid);
	if(mid < r) mul(l, r, d, p << 1 | 1, mid + 1, cr);
	t1[p].sum = (t1[p << 1].sum + t1[p << 1 | 1].sum) % mod;
	t2[p].sum = (t2[p << 1].sum + t2[p << 1 | 1].sum) % mod;
}
ll q_pow(ll a, ll n, ll ans = 1){
	while(n){
		if(n & 1) ans = ans * a % mod; a = a * a % mod; n >>= 1;
	}return ans;
}
void work()
{
	cin >> n >> m >> mod;
	for(int i = 1; i <= n; ++i) cin >> a[i];
	build();
	while(m--)
	{
		int op, l, r;
		cin >> op >> l >> r;
		if(op == 2) {
			int k;cin >> k;mul(l, r, k);
		}
		else if(op == 1){
			int k;cin >> k;add(l, r, k);
		}
		else 
		{
			ll x = query(t1, l, r) % mod;
			x = x * x % mod;
			ll y = query(t2, l, r) % mod;
			cout << ((x - y) % mod + mod) % mod * q_pow(2, mod - 2) % mod << endl;
		}
	}
}
int main()
{
	ios::sync_with_stdio(0);
	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值