P3373 【模板】线段树 2 题解

博客园同步

原题链接

前置知识:

线段树 区间查询 / 区间修改

简要题意:

维护数组的区间加,乘,区间和。

首先,如果没有乘的话,直接把 P3372 【模板】线段树1 的代码复制过来进行了。

那么,你会说:

  • 那多简单,用两个标记,然后加的时候改加,乘的时候改乘。

真的是这样的吗?是还用我跟你讲啊

不是。

比方说,一个区间原来的和是 a a .假设给它依次打上 + 2 +2 × 3 \times 3 + 4 +4 × 5 \times 5 的标记,并用 mul \text{mul} 表示乘法标记(初始为 1 1 ), add \text{add} 表示 加法标记(初始为 0 0 ).

那么,按照你说的,应该是:

  • + 2 +2 ,则 add=2,mul=1 \text{add=2,mul=1} .

  • × 3 \times 3 ,则 add=2,mul=3 \text{add=2,mul=3} .

好了,这里就已经错了。你 × 3 \times 3 的时候,其实结果应该是:

( a + 2 ) × 3 = 3 a + 6 (a+2) \times 3 = 3a + 6 ,所以你的加法标记 碰到加法是累加,碰到乘法是累乘!

那么,计算优先级呢?当然是先乘法啦。

因为,如果 a × 3 + 2 a \times 3 + 2 的话,你先算加法就是 ( a + 2 ) × 3 (a+2) \times 3 ,然后就错了。因为你 根据乘法分配律把加法已经维护完了,最后一定是 a × m u l + a d d a \times mul + add 的形式。

然后,标记的下传有一些细节。

另附一段对话(自编):

  • 线段树:唉我真是烦死了,标记要是全下传了不就不如你了吗,嗯?

  • 暴力:那怎么行,你可是 提高组算法,我是入门组算法啊,你不要下传标记就完了呗。

  • 线段树:嗯?那怎么行?

  • 暴力:没有人来询问它,你就别下传啊。等到有人询问的时候,你再下传一步,反正又不影响?

  • 线段树:真好。可两个加、乘标记混合了怎么办啊。。。

  • 暴力:你应该。。@#$^&%!$# ……*&%¥&@34%#! \cdots \cdots \cdots

  • 线段树:我 *** !!!偷懒都这么难的么。。

  • 暴力:你不难,你还是提高组算法么?看好了,这可是绿题!要是 我能解决还要你干嘛呢?是不是?

  • 线段树:行,我先脑补一下。。。

  • 暴力:要不是 CCF 老年机一秒跑不了 1 0 12 10^{12} 次,我还要你线段树????

时间复杂度: O ( n log n ) O(n \log n) .

实际得分: 100 p t s 100pts .

//与线段树 1 相同的部分不再注释
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

#define L (i<<1)
#define R (i<<1)+1

typedef long long ll;

inline ll read(){char ch=getchar();ll f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	ll x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

const ll N=1e5+1;
ll n,m,MOD;

struct node{
	ll l,r,sum;
	ll mul,add;
};
node t[4*N]; ll a[N];

inline void update(ll i) {
	t[i].sum=(t[L].sum+t[R].sum)%MOD;
}

inline void build_tree(ll i,ll l,ll r) {
	t[i].l=l; t[i].r=r; t[i].mul=1;
	if(l==r) {t[i].sum=a[l]%MOD;return;}
	ll mid=(l+r)>>1;
	build_tree(L,l,mid);
	build_tree(R,mid+1,r);
	update(i);
}

inline void pushdown(ll i) {
	t[L].sum=(t[i].mul*t[L].sum+((t[L].r-t[L].l+1)*t[i].add)%MOD)%MOD;
	t[R].sum=(t[i].mul*t[R].sum+((t[R].r-t[R].l+1)*t[i].add)%MOD)%MOD; //根据区间长度更新区间和
	t[L].mul=(t[L].mul*t[i].mul)%MOD;
	t[R].mul=(t[R].mul*t[i].mul)%MOD; //乘法标记累乘
	t[L].add=(t[i].mul*t[L].add+t[i].add)%MOD;
	t[R].add=(t[i].mul*t[R].add+t[i].add)%MOD; //加法标记先乘后加
	t[i].mul=1; t[i].add=0; //记得甩锅
}

inline void change_add(ll i,ll l,ll r,ll k) {
	if(l<=t[i].l && t[i].r<=r) {
		t[i].add=(t[i].add+k)%MOD;
		t[i].sum=(t[i].sum+k*(t[i].r-t[i].l+1))%MOD;
		return; //更新区间和
	} pushdown(i); update(i); 
	ll mid=(t[i].l+t[i].r)>>1;
	if(l<=mid) change_add(L,l,r,k);
	if(r>mid)  change_add(R,l,r,k);
	update(i); 
}

inline void change_mul(ll i,ll l,ll r,ll k) {
	if(l<=t[i].l && t[i].r<=r) {
		t[i].add=(t[i].add*k)%MOD;
		t[i].mul=(t[i].mul*k)%MOD; //更新区间乘
		t[i].sum=(t[i].sum*k)%MOD; return;
	} pushdown(i); update(i);
	ll mid=(t[i].l+t[i].r)>>1;
	if(l<=mid) change_mul(L,l,r,k);
	if(r>mid)  change_mul(R,l,r,k);
	update(i);
}

inline ll querysum(ll i,ll l,ll r) {
	if(l<=t[i].l && t[i].r<=r) return t[i].sum;
	pushdown(i);
	ll mid=(t[i].l+t[i].r)>>1,ans=0;
	if(l<=mid) ans=(ans+querysum(L,l,r))%MOD;
	if(r>mid)  ans=(ans+querysum(R,l,r))%MOD;
	return ans; 
}

int main(){
	n=read(),m=read(),MOD=read();
	for(ll i=1;i<=n;i++) a[i]=read();
	build_tree(1,1,n); while(m--) {
		ll op=read(),x=read(),y=read(),k;
		if(op==1) k=read(),change_mul(1,x,y,k);
		if(op==2) k=read(),change_add(1,x,y,k);
		if(op==3) printf("%lld\n",querysum(1,x,y));
	}
	return 0;
}
发布了41 篇原创文章 · 获赞 45 · 访问量 2964

猜你喜欢

转载自blog.csdn.net/bifanwen/article/details/105310587