• 洛谷P3373 【模板】线段树 2


    洛谷P3373 【模板】线段树 2

    题目描述

    如题,已知一个数列,你需要进行下面两种操作:

    1.将某区间每一个数加上x

    2.将某区间每一个数乘上x

    3.求出某区间每一个数的和

    输入输出格式

    输入格式:

     

    第一行包含三个整数N、M、P,分别表示该数列数字的个数、操作的总个数和模数。

    第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

    接下来M行每行包含3或4个整数,表示一个操作,具体如下:

    操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k

    操作2: 格式:2 x y k 含义:将区间[x,y]内每个数加上k

    操作3: 格式:3 x y 含义:输出区间[x,y]内每个数的和对P取模所得的结果

     

    输出格式:

     

    输出包含若干行整数,即为所有操作3的结果。

     

    输入输出样例

    输入样例#1:
    5 5 38
    1 5 4 2 3
    2 1 4 1
    3 2 5
    1 2 4 2
    2 3 5 5
    3 1 4
    输出样例#1:
    17
    2

    说明

    时空限制:1000ms,128M

    数据规模:

    对于30%的数据:N<=8,M<=10

    对于70%的数据:N<=1000,M<=10000

    对于100%的数据:N<=100000,M<=100000

    (数据已经过加强^_^)

    题解:来敲第二发线段树模板。

    针对这样的模板题,比只含加操作的情况又复杂一点。如果你对线段树的操作还不熟悉,请参见线段树模板1。网址链接:http://www.cnblogs.com/zk1431043937/p/7737021.html

    如果你明白了线段树模板1,相信你对这题升级版应该也有思路了吧。

    在这里先介绍一下在线段树模板1中并未提及的懒标记的作用。

    在线段树模板1中,我们打了懒标记,懒标记的作用是提高效率,因为有些情况下并不需要真的直接完全下放懒标记下放到底。

    我们可以想象一下,如果修改的区间是[1,n],不用懒标记,就会直接将约2n个节点全部遍历一遍,如果m个操作全都是修改区间[1,n],那么复杂度会退化到O(2MN),再乘上DFS的大常数,比直接暴力O(MN)的效率还要低下得多。

    而懒标记的存在就会大大提高效率,它会将标记打在恰好能覆盖到整个区间的一些线段上,一直累积,直到要往下遍历时才会下放,这样就避免了不必要的操作,使得复杂度达到O(Mlog2N)。

    好了,介绍完懒标记的作用,接下来让我们看一看懒标记和本题的关系。

    在线段树模板1中,打的懒标记很显然是储存加上的数值的,那么针对此题又能有什么新的想法呢?

    显然,我们就需要使用两个懒标记了。在这里,我们采用mul作为乘法标记,add作为加法标记。

    本题的整体框架与线段树模板1基本相同,只是处理区间修改和懒标记时,针对不同的运算来分开写。

    这里特别提一下两个懒标记的下放运算,以免出错。

    对于加法运算,与线段树模板1没有太大区别,因为乘法的优先级本身比加法高,所以现在的加法运算对之前的乘法运算无影响,只需要注意取模。

    对于乘法运算,因为乘法的优先级本身比加法高,所以现在的乘法运算对之前的加法运算有影响,相当于要在前面的所有运算上整体加一个小括号,然后再乘上该数。我们来分析一下:

    假设当前节点的其中一个儿子节点已有mul标记和add标记,那么将当前节点的mul标记下放时,应该使儿子节点的mul标记、add标记和区间值v都乘以当前节点的v并取模,利用乘法分配律的性质可以证出来。

    还有要注意的是懒标记下放完,add清零,mul变为1。因为mul清零的话,一个数乘以0就会一直是零,这样运算出的答案很显然是错误的,因此要变为1。所以建树时,初始化懒标记也应该这样。

    在这里,我们就把add不为0时,看作有add标记,mul不为1时(特别注意mul是0时也有标记,因为mul为1才是初始状态,为0是其实是有标记的),看作有mul标记,此时是需要下放的。

    最后,注意如果一个节点同时有mul标记和add标记时应先下放mul标记,再下放add标记,因为乘法的优先级大于加法。

    在这里,有些人可能会疑惑如果在之前一系列的运算中乘法和加法先后顺序并不是先乘后加怎么办?结论不就不成立了吗?提出这样的想法是有道理的,但其实不可能存在这样的情况。因为我们下放mul懒标记时进行的乘法分配律运算避免了这样的情况发生。

    想明白这些,这题就完美解决了。时间复杂度为O(Mlog2N)。

    下面附上代码。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int N=100005;
     4 struct node{
     5     int l,r; long long v,mul,add;
     6 }a[N<<2];
     7 int n,m,opt,x,y;
     8 long long p,k;
     9 void build(int u,int l,int r)
    10 {
    11     a[u].l=l; a[u].r=r; a[u].mul=1; a[u].add=0;
    12     if (l==r) scanf("%lld",&a[u].v);
    13     else
    14     {
    15         int mid=(l+r)>>1;
    16         build(u<<1,l,mid);
    17         build(u<<1|1,mid+1,r);
    18         a[u].v=a[u<<1].v+a[u<<1|1].v;
    19     }
    20 }
    21 void apply_mul(int u,long long v)
    22 {
    23     a[u].mul=a[u].mul*v%p;
    24     a[u].add=a[u].add*v%p;
    25     a[u].v=a[u].v*v%p;
    26 }
    27 void apply_add(int u,long long v)
    28 {
    29     a[u].add+=v; a[u].add%=p;
    30     a[u].v+=(a[u].r-a[u].l+1)*v; a[u].v%=p;
    31 }
    32 void push_down(int u)
    33 {
    34     if (a[u].mul!=1)
    35     {
    36         apply_mul(u<<1,a[u].mul);
    37         apply_mul(u<<1|1,a[u].mul);
    38         a[u].mul=1;
    39     }
    40     if (a[u].add)
    41     {
    42         apply_add(u<<1,a[u].add);
    43         apply_add(u<<1|1,a[u].add);
    44         a[u].add=0;
    45     }
    46 }
    47 void change_mul(int u,int l,int r,long long v)
    48 {
    49     if (a[u].l==l&&a[u].r==r) apply_mul(u,v);
    50     else
    51     {
    52         int mid=(a[u].l+a[u].r)>>1;
    53         push_down(u);
    54         if (r<=mid) change_mul(u<<1,l,r,v);
    55         else if (l>mid) change_mul(u<<1|1,l,r,v);
    56         else change_mul(u<<1,l,mid,v),change_mul(u<<1|1,mid+1,r,v);
    57         a[u].v=a[u<<1].v+a[u<<1|1].v;
    58     }
    59 }
    60 void change_add(int u,int l,int r,long long v)
    61 {
    62     if (a[u].l==l&&a[u].r==r) apply_add(u,v);
    63     else
    64     {
    65         int mid=(a[u].l+a[u].r)>>1;
    66         push_down(u);
    67         if (r<=mid) change_add(u<<1,l,r,v);
    68         else if (l>mid) change_add(u<<1|1,l,r,v);
    69         else change_add(u<<1,l,mid,v),change_add(u<<1|1,mid+1,r,v);
    70         a[u].v=a[u<<1].v+a[u<<1|1].v;
    71     }
    72 }
    73 long long query(int u,int l,int r)
    74 {
    75     if (a[u].l==l&&a[u].r==r) return a[u].v;
    76     int mid=(a[u].l+a[u].r)>>1;
    77     push_down(u);
    78     if (r<=mid) return query(u<<1,l,r);
    79     else if (l>mid) return query(u<<1|1,l,r);
    80     return (query(u<<1,l,mid)+query(u<<1|1,mid+1,r))%p;
    81 }
    82 int main()
    83 {
    84     scanf("%d%d%lld",&n,&m,&p);
    85     build(1,1,n);
    86     for (int i=1;i<=m;++i)
    87     {
    88         scanf("%d",&opt);
    89         if (opt==1) scanf("%d%d%lld",&x,&y,&k),change_mul(1,x,y,k);
    90         if (opt==2) scanf("%d%d%lld",&x,&y,&k),change_add(1,x,y,k);
    91         if (opt==3) scanf("%d%d",&x,&y),printf("%lld
    ",query(1,x,y));
    92     }
    93     return 0;
    94 }
    View Code
  • 相关阅读:
    变量可变性问题
    Android 创建Listener监听器形式选择:匿名内部类?外部类?
    linux下安装zookeeper
    翻页工具类
    将哈夫曼树转化成二叉树
    Activity的启动流程分析
    题目1186:打印日期
    数据库设计--数据流图(DFD)
    c#基础之数组
    10.3.1 一个CONNECT BY的样例
  • 原文地址:https://www.cnblogs.com/zk1431043937/p/7738348.html
Copyright © 2020-2023  润新知