题目传送: P3373 【模板】线段树 2  P2023 [AHOI2009]维护序列

该题较传统线段树模板相比多了一个区间乘的操作。一提到线段树的区间维护问题,就自然想到了“懒标记”:为了降低时间复杂度,我们只需将要要查询的区间的真实值更新出来,而不至于一直细分到区间的每个单元,并给更新真实值的区间附加一个“懒标记”,表示他的后代们还没有被更新。但是题中既有区间加,又有区间乘,一个懒标记难以轻松地同时把加和乘表示,怎么办呢?用两个懒标记不就好了嘛。设lzsum[i]、lzmul[i]分别为i节点表示加的懒标记和表示乘的懒标记。

接下来如何下传懒标记呢?这就看我们要怎么用懒标记由标记前的状态表示标记后的状态了。因为只有加和乘,我们考虑先加还是先乘。

  先加的话,用懒标记更新状态的方程即为tree[son]=(tree[son]+lzsum[father])*lzmul[father]。这个式子一看就让人摸不到维护lzsum和lzmul的头绪,很难适用于编程实现,看看另一种情况吧;

  先乘的话,用懒标记更新状态的方程即为tree[son]=tree[son]*lzmul[father]+lzsum[father]*区间长度。分析一下这个式子,设想当再乘一个数k时,tree[son]直接*=k就OK了,这样不就相当于原式中的lzmul[father]乘上个kk且lzsum[father]也乘上个k吗(乘法分配律)?如果再加一个数k的话,只需让式子中的lzsum[father]+k就行了。这样我们就找到了一个下传的策略:先乘再加。同时我们还分析出了更新lzsum和lzmul的方法。

  看到这里,不难发现懒标记的实际意义就是目前区间的每个元素较他自己有懒标记前的值在一顿区间加区间乘操作后等效于要乘几倍后再加几。由此知,当懒标记由父节点下传、去更新儿子的真实值时,父亲懒标记代表的“要乘几倍后再加几”,就是儿子“要乘几倍后再加几”。

  题中提到数据太大、输出取模后的结果,那就在算式里多模几下就行了。

上AC代码!:

 #include<iostream>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<stack>
#include<cmath>
using namespace std;
long long tree[],mod,a[],lzsum[],lzmul[];
long long ans;
char ch;
long long read()
{
ans=;
ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) ans=(ans<<)+(ans<<)+ch-'',ch=getchar();
return ans;
}
void build(int t,int l,int r)//线段树的初始化
{
if(l==r) tree[t]=a[l];
else
{
int mid=(l+r)/,ls=t*,rs=ls+;
build(ls,l,mid);
build(rs,mid+,r);
tree[t]=(tree[ls]+tree[rs])%mod;
}
lzmul[t]=;
}
stack<char>pri;
void print(long long a)//输出优化,显效甚微,嫌麻烦用scanf就行。
{
if(!a)//写这样的输出优化别忘了在判断a=0的时候。(因为下文默认a>0。如果开头不特判的话,
//当a=0时程序会直接跳过两个while,只会输出一个回车)
{
putchar('');
putchar('\n');
return;
}
while(a>)
{
pri.push(a%+'');
a/=;
}
while(!pri.empty())
{
putchar(pri.top());
pri.pop();
}
putchar('\n');
}
void down(int t,int l,int r)//懒标记的下传
{
if(lzsum[t]==&&lzmul[t]==) return;
int ls=t*,rs=ls+;
tree[ls]=(tree[ls]*lzmul[t]+lzsum[t]*((r-l+)/))%mod;
tree[rs]=(tree[rs]*lzmul[t]+lzsum[t]*((r-l+)/))%mod;
lzmul[ls]=(lzmul[ls]*lzmul[t])%mod;
lzmul[rs]=(lzmul[rs]*lzmul[t])%mod;
lzsum[ls]=(lzsum[ls]*lzmul[t]+lzsum[t])%mod;
lzsum[rs]=(lzsum[rs]*lzmul[t]+lzsum[t])%mod;
lzsum[t]=;
lzmul[t]=;//注意lzmul的初始状态应该为一,因为一个数乘1才等于它本身
}
void mul(int t,int l,int r,int ll,int rr,int k)
{
if(ll<=l&&r<=rr)
{
tree[t]=(tree[t]*k)%mod;
lzmul[t]=(lzmul[t]*k)%mod;
lzsum[t]=(lzsum[t]*k)%mod;
return;
}
down(t,l,r);
int mid=(l+r)/,ls=t*,rs=ls+;
if(ll<=mid) mul(ls,l,mid,ll,rr,k);
if(rr>mid) mul(rs,mid+,r,ll,rr,k);
tree[t]=(tree[ls]+tree[rs])%mod;//儿子更新可不能忘了爹啊
}
void sum(int t,int l,int r,int ll,int rr,int k)
{
if(ll<=l&&r<=rr)
{
tree[t]=(tree[t]+k*(r-l+))%mod;
lzsum[t]=(lzsum[t]+k)%mod;
return;
}
down(t,l,r);
int ls=t*,rs=ls+,mid=(l+r)/;
if(ll<=mid) sum(ls,l,mid,ll,rr,k);
if(rr>mid) sum(rs,mid+,r,ll,rr,k);
tree[t]=(tree[ls]+tree[rs])%mod;
}
void fin(int t,int l,int r,int ll,int rr)
{
if(ll<=l&&r<=rr)
{
ans=(ans+tree[t])%mod;
return;
}
down(t,l,r);
int ls=t*,rs=ls+,mid=(l+r)/;
if(ll<=mid) fin(ls,l,mid,ll,rr);
if(rr>mid) fin(rs,mid+,r,ll,rr);
}
int main()
{
int n=read(),m;
mod=read();
for(int i=;i<=n;i++)
a[i]=read();
build(,,n);
m=read();
int order,l,r,k;
for(int i=;i<=m;i++)
{
order=read();
if(order==)
{
l=read(),r=read(),k=read();
mul(,,n,l,r,k);
}
if(order==)
{
l=read(),r=read(),k=read();
sum(,,n,l,r,k);
}
if(order==)
{
l=read(),r=read();
ans=;
fin(,,n,l,r);
print(ans);
}
}
return ;
}

洛谷P3373 【模板】线段树 2 && P2023 [AHOI2009]维护序列——题解的更多相关文章

  1. 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)

    To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...

  2. 洛谷 P2023 [AHOI2009]维护序列 题解

    P2023 [AHOI2009]维护序列 题目描述 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,-,aN .有如下三种操作形式: (1)把数列中 ...

  3. P2023 [AHOI2009]维护序列 题解(线段树)

    题目链接 P2023 [AHOI2009]维护序列 解题思路 线段树板子.不难,但是...有坑.坑有多深?一页\(WA\). 由于乘法可能乘\(k=0\),我这种做法可能会使结果产生负数.于是就有了这 ...

  4. 【线段树】Bzoj1798 [AHOI2009] 维护序列

    Description 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2 ...

  5. 洛谷P2023 [AHOI2009]维护序列(线段树区间更新,区间查询)

    洛谷P2023 [AHOI2009]维护序列 区间修改 当我们要修改一个区间时,要保证 \(ax+b\) 的形式,即先乘后加的形式.当将区间乘以一个数 \(k\) 时,原来的区间和为 \(ax+b\) ...

  6. 洛谷 P2023 [AHOI2009]维护序列

    P2023 [AHOI2009]维护序列 题目描述 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中 ...

  7. [洛谷P2023] [AHOI2009]维护序列

    洛谷题目链接:[AHOI2009]维护序列 题目描述 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,-,aN .有如下三种操作形式: (1)把数列 ...

  8. 线段树_区间加乘(洛谷P3373模板)

    题目描述 如题,已知一个数列,你需要进行下面三种操作: 1.将某区间每一个数乘上x 2.将某区间每一个数加上x 3.求出某区间每一个数的和 输入格式: 第一行包含三个整数N.M.P,分别表示该数列数字 ...

  9. 【题解】洛谷P2023 [AHOI2009] 维护序列(线段树)

    洛谷P2023:https://www.luogu.org/problemnew/show/P2023 思路 需要2个Lazy-Tag 一个表示加的 一个表示乘的 需要先计算乘法 再计算加法 来自你谷 ...

随机推荐

  1. 伯努利分布、二项分布、多项分布、Beta分布、Dirichlet分布

    1. 伯努利分布 伯努利分布(Bernoulli distribution)又名两点分布或0-1分布,介绍伯努利分布前首先需要引入伯努利试验(Bernoulli trial). 伯努利试验是只有两种可 ...

  2. Mac020--常用插件

    Google浏览器常用插件 1.github插件octotree 2.掘金Chrome网上应用商店 2-1.掘金/老司机的神兵利器 2-2.好用的Google插件:来自掘金 3.Gliffy Diag ...

  3. 20191112 Spring Boot官方文档学习(4.5-4.6)

    4.5.国际化 Spring Boot支持本地化消息,因此您的应用程序可以迎合不同语言首选项的用户.默认情况下,Spring Boot messages在类路径的根目录下查找message resou ...

  4. swagger @ApiModel添加实体类不生效

    在使用swagger时, 以为加上@ApiModel在实体类上就可以在swagger-ui.html的Models里面显示. 但是我创建了很多实体类, 但怎么也只显示了一个??? Models中无论如 ...

  5. tensorflow学习笔记三----------基本操作

    tensorflow中的一些操作和numpy中的很像,下面列出几个比较常见的操作 import tensorflow as tf #定义三行四列的零矩阵 tf.zeros([3,4]) #定义两行三列 ...

  6. xss过滤与单例模式(对象的实例永远用一个)

    kindeditor里面可以加入script代码,使用re可以过滤掉python有个专门的模块可以处理这种情况,beautifulsoup4 调用代码: content = XSSFilter().p ...

  7. C# 静态方法 静态属性 调用静态方法

    C#的类中可以包含两种方法:静态方法和非静态方法. 使用了static 修饰符的方法为静态方法,反之则是非静态方法. 静态方法是一种 特殊的成员方法,它不属于类的某一个具体的实例,而是属于类本身.所以 ...

  8. tar.xz问价解压

    1. 解压tar.xz安装包 今天去Ubuntu上安装nodejs,下载的文件是node-v8.11.1-linux-x64.tar.xz,这是两层压缩,外面是xz压缩,里层是tar压缩,所以分两步实 ...

  9. oracle数据库ID自增长--序列

    什么是序列?在mysql中有一个主键自动增长的id,例如:uid number primary key auto_increment;在oracle中序列就是类似于主键自动增长,两者功能是一样的,只是 ...

  10. css实现斑马线效果

    文本实现斑马线效果 <style> p { font-size: 17px; line-height: 25px; background-color: antiquewhite; back ...