首页 > 其他分享 >线段树(3)——区间操作叠加

线段树(3)——区间操作叠加

时间:2024-08-25 14:26:01浏览次数:10  
标签:multi int 线段 tr 叠加 tl 区间 lazy now

如果我既有区间乘法又有区间加法,我应该怎么办呢?

这时候需要写两个标记。假设只写一个标记。

标记加法:此时对于乘法操作,因为是将 \(t_i+lazy_i\) 乘以 \(x\),这样子显然一个懒惰标记做不到。

标记乘法:那我加法咋办?

那两个标记怎么用呢?首先假设加法标记为 \(lazy\),乘法标记为 \(multi\)。如果将这两个标记分开显然是不可能的,因为每个点的标记是互相依赖的。而如果一直 \(push\_down\) 需要花费 \(\log n\) 的时间,这与线段树 \(\log n\) 的标准就不一样了,别忘了还有线段树本身的操作。如果仅仅是给 \(now\) 下放,那么子结点会遇到一样的问题。一般我们做题用的线段树数量是根据查询的种类来的。假设这里只有区间加法查询,那么应该怎么做呢?

首先考虑 \(t\) 的变化,显然在加法操作时,应当 \(t_{now}+=x\times (tr-tl+1)\),在乘法操作时,应当 \(t_{now}\times =x\)。其次,考虑 \(push\_down\),对于加法的 \(push\_down\),和普通的加法下放一样,因为加法下方是不考虑乘法标记的,而乘法标记需要考虑加法标记。如果反过来,加法下放考虑乘法标记,这时候 \(lazy_{now}\) 就可能出现分数,就不太方便了。那么考虑乘法下放。对于一个区间,如果统统乘以 \(x\),那么这个区间的和就乘以 \(x\),这是乘法分配律。所以,乘法标记下放的时候应该让 \(t_{now*2(+1)}\) 和 \(lazy\)_{now*2(+1)} 都乘以 \(multi_{now}\)。那么如果将一个区间都乘以 \(x\) 再都乘以 \(y\),就相当于乘以 \(xy\),所以 \(mutli_{now*2(+1)}\) 应当乘以 \(multi_{now}\)。然后将 \(multi_now\) 清为 \(1\)。在写修改和查询的时候,要先下放乘法标记,再下放加法标记,因为乘法标记包含对加法的考虑,所以先下放加法会出现各种各样的问题。

以下代码参考题目

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,p,m,a[N],t[N*8],lazy[N*8],multi[N*8];
void push_down_add(int now,int tl,int tr){
    int mid=(tl+tr)/2;
    t[now*2]=(t[now*2]+lazy[now]*(mid-tl+1))%p;
    t[now*2+1]=(t[now*2+1]+lazy[now]*(tr-mid))%p;
    lazy[now*2]=(lazy[now*2]+lazy[now])%p;
    lazy[now*2+1]=(lazy[now*2+1]+lazy[now])%p;
    lazy[now]=0;
}
void push_down_multi(int now,int tl,int tr){
    int mid=(tl+tr)/2;
    t[now*2]=t[now*2]*multi[now]%p;
    t[now*2+1]=t[now*2+1]*multi[now]%p;
    multi[now*2]=multi[now*2]*multi[now]%p;
    multi[now*2+1]=multi[now*2+1]*multi[now]%p;
    lazy[now*2]=lazy[now*2]*multi[now]%p;
    lazy[now*2+1]=lazy[now*2+1]*multi[now]%p;
    multi[now]=1;
}
void build(int now,int tl,int tr){
    multi[now]=1;
    if(tl==tr){
        t[now]=a[tl];
        return ;
    }
    int mid=(tl+tr)/2;
    build(now*2,tl,mid);
    build(now*2+1,mid+1,tr);
    t[now]=(t[now*2]+t[now*2+1])%p;
}
void modify_add(int now,int tl,int tr,int l,int r,int x){
    if(tl>=l&&tr<=r){
        t[now]=(t[now]+x*(tr-tl+1))%p;
        lazy[now]=(lazy[now]+x)%p;
        return ;
    }
    if(tl>r||tr<l){
        return ;
    }
    if(multi[now]!=1){
        push_down_multi(now,tl,tr);
    }
    if(lazy[now]){
        push_down_add(now,tl,tr);
    }
    int mid=(tl+tr)/2;
    modify_add(now*2,tl,mid,l,r,x);
    modify_add(now*2+1,mid+1,tr,l,r,x);
    t[now]=(t[now*2]+t[now*2+1])%p;
}
void modify_multi(int now,int tl,int tr,int l,int r,int x){
    if(tl>=l&&tr<=r){
        t[now]=t[now]*x%p;
        multi[now]=multi[now]*x%p;
        lazy[now]=lazy[now]*x%p;
        return ;
    }
    if(tl>r||tr<l){
        return ;
    }
    if(multi[now]!=1){
        push_down_multi(now,tl,tr);
    }
    if(lazy[now]){
        push_down_add(now,tl,tr);
    }
    int mid=(tl+tr)/2;
    modify_multi(now*2,tl,mid,l,r,x);
    modify_multi(now*2+1,mid+1,tr,l,r,x);
    t[now]=(t[now*2]+t[now*2+1])%p;
}
int query(int now,int tl,int tr,int l,int r){
    if(tl>=l&&tr<=r){
        return t[now];
    }
    if(tl>r||tr<l){
        return 0;
    }
    if(multi[now]!=1){
        push_down_multi(now,tl,tr);
    }
    if(lazy[now]){
        push_down_add(now,tl,tr);
    }
    int mid=(tl+tr)/2;
    return (query(now*2,tl,mid,l,r)+query(now*2+1,mid+1,tr,l,r))%p;
}
signed main(){
    //freopen("xx.in","r",stdin);
    //freopen("xx.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>p;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    build(1,1,n);
    while(m--){
        int opt,t,g,c;
        cin>>opt;
        if(opt==1){
            cin>>t>>g>>c;
            modify_multi(1,1,n,t,g,c);
        }else if(opt==2){
            cin>>t>>g>>c;
            modify_add(1,1,n,t,g,c);
        }else{
            cin>>t>>g;
            cout<<query(1,1,n,t,g)<<"\n";
        }
    }
    return 0;
}

标签:multi,int,线段,tr,叠加,tl,区间,lazy,now
From: https://www.cnblogs.com/wuyiming1263/p/18377184

相关文章

  • 区间众数(分块)
    题目描述给定一个序列\(a_1,a_2,\dots,a_n\),\(m\)个询问。每个询问指定一个区间\([l,r]\),你需要输出\(a_l,a_{l+1},\dots,a_r\)这些数字里出现次数最多的数的出现次数。输入第一行一个整数\(T(1\leqT\leq6)\),表示测试数据的组数。每组数据第一行两个数\(n,m(1\leqn,m\leq......
  • 线段树维护单调栈——区间查询版本 & 维护递减序列
    这次算是完全搞懂了吧()()(#include<bits/stdc++.h>#defineraedread#definecaclcalc#definepbpush_back#definepiipair<int,int>#defineintlonglong#definefifirst#definesesecond#definelsp<<1#definersp<<1|1usingn......
  • 维护区间信息
    维护区间信息(在线)暴力做法,\(O(n)\)修改,\(O(n)\)查询。但我们会发现多次询问会重复查询一些点,所以我们可以记录下一些区间的信息,查询时就可以节约时间。但我们记录的区间必须满足一些优秀性质:灵活性:记录下的区间组合灵活性高,即查询区间可以尽可能被记录下来的区间记录下来。......
  • 线段树(2)——懒惰标记Lazy Tag(单运算)及例题
    上一篇文章我们讲了线段树的最基本的操作。如果有一种操作叫做区间加法呢?这个时候显然可以依次单点修改,但是时间复杂度太高了。所以可以考虑优化,由于思考过程可能很长,此处直接引入懒惰标记。懒惰标记就是在对一颗树的所有节点进行某种统一操作时,只对根节点做一个标记表示它的子树......
  • 序列划分(区间DP)
    题目描述\(n\)个人,每个人手上有一个数\(a_i\)。将这些人分成若干组,组没有编号,要求每组人手上的数字之和都是质数。求合法的分组方案数。输入第一行一个正整数\(T(1\leqT\leq5)\),表示测试数据的组数。每组数据第一行一个正整数\(n(1\leqn\leq15)\)。每组数据第二行\(n\)......
  • 代码随想录 -- 数组 -- 区间和
    58.区间和(第九期模拟笔试)(kamacoder.com)暴力解法大概率超时,应采用前缀和解法p[i] 表示vec[0]到vec[i]的累加和求vec[m] 到vec[n] 的和只需要 p[n]-p[m] 即可知识点input函数Python3 中raw_input()和input()进行了整合,去除了raw_input(),仅保留了i......
  • 线段树进阶
    线段树进阶目录线段树进阶线段树+贪心/线段树+排序例题:1.洛谷P1607FairShuttleG2.洛谷P1937BarnAllocationG3.洛谷P1972HH的项链线段树+双指针例题:1.洛谷P1712区间线段树+多个标记维护例题:1.洛谷P2572序列操作线段树+二分例题:1.洛谷P4344脑洞治疗仪2.洛谷P2824排......
  • 线段树 Ⅰ
    前言线段树用于维护数列区间上的值,可以在\(O(\logn)\)的时间复杂度内实现单点,区间修改及查询。并且所有满足结合律的运算都可以用线段树进行加速。基本操作建树给你长度为\(n\)的正整数序列\(A\),要你实现单点修改与区间查询。(加和)这就是线段树的基本题目。线段树,字面上......
  • 题解:P10724 [GESP202406 七级] 区间乘积
    思路当一个数是完全平方数的时候,它的所有质因子的次数都是偶数。记\(x\)的质因子为\(p_1^{q1}\timesp_2^{q2}\timesp_3^{q3}...\timesp_v^{qv}\)。这些数可以通过次数的奇偶性用一个\(v\)位的二进制串\(B\)表示,\(B_i\)为\(0\)说明\(q_i\)为偶数,\(B_i\)为\(......
  • 权值线段树与动态开点线段树
    权值线段树(维护一段值域)用线段树维护桶实质上是维护一段值域中数字出现次数例:\(1,5,4,6,7,3,8,4,5,6\);根:\(1-8\);左儿子:\(1-4\);右儿子:\(5-8\);询问目前出现第\(k\)小数字从根节点出发,如果根节点权值\(>k\)则证明存在第\(k\)小;以此类推问:如果值域很大,线段树炸了怎......