zoukankan      html  css  js  c++  java
  • P3373 【模板】线段树 2 题解

    CSDN同步

    原题链接

    前置知识:

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

    简要题意:

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

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

    那么,你会说:

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

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

    不是。

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

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

    • (+2),则 ( ext{add=2,mul=1}).

    • ( imes 3),则 ( ext{add=2,mul=3}).

    好了,这里就已经错了。你 ( imes 3) 的时候,其实结果应该是:

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

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

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

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

    另附一段对话(自编):

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

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

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

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

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

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

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

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

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

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

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

    实际得分:(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
    ",querysum(1,x,y));
    	}
    	return 0;
    }
    
  • 相关阅读:
    谷粒商城心得(四)
    centos7设置rc.local开机执行命令
    密码学简介
    如何解决 kubernetes 重启后,启来不来的问题
    谷粒商城安装ES及入门(十六)
    谷粒商城读写分离(十五)
    谷粒商城创建mysql主从(十四)
    虚拟机LVM在线扩容
    Builder 模式初探
    Mysql 导入实战
  • 原文地址:https://www.cnblogs.com/bifanwen/p/12631953.html
Copyright © 2011-2022 走看看