题目传送: 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. Numpy——进阶篇

    impoort numpy as np arr=np.arange(10) #输出奇数 arr[arr%2==1] #将arr中的所有奇数替换为-1,而不改变arr out=np.where(arr% ...

  2. 20191128 Spring Boot官方文档学习(9.4-9.8)

    9.4.Spring MVC Spring Boot有许多启动器包含Spring MVC.请注意,一些启动器包括对Spring MVC的依赖,而不是直接包含它. 9.4.1.编写JSON REST服务 ...

  3. 第二次课程总结&学习总结

    Java实验报告 班级 计算机科学与技术一班 学号 20188390 姓名 宋志豪 实验 写一个名为Rectangle的类表示矩形.其属性包括宽width.高height和颜色color,width和 ...

  4. 第四周课程总结与第二次实验报告(Java简单类与对象)

    1.写一个名为Rectangle的类表示矩形.其属性包括宽width.高height和颜色color,width和height都是double型的,而color则是String类型的.要求该类具有: ...

  5. 华南理工大学 “三七互娱杯” G HRY and tree

    https://ac.nowcoder.com/acm/contest/874/G 题目大意:对于一个连通图,现在定义两个点的贡献为连接两点的路径上最大的权值 求任意两个点贡献的和 这个题看懂花了我很 ...

  6. [转帖]22款让Kubernetes锦上添花的开源工具

    22款让Kubernetes锦上添花的开源工具 http://soft.zhiding.cn/software_zone/2019/0506/3117650.shtml 找时间尝试一下. 至顶网软件频 ...

  7. mysql5.7单机多实例安装

    基于之前的mysql5.7单实例安装 修改/etc/my.cnf文件如下(这里配置4个实例,可自行修改数目) # # 多实例配置文件,可以mysqld_multi --example 查看例子 # [ ...

  8. Linux下查看日志用到的常用命令

    杀僵尸进程 部分程序员,肯定喜欢下面命令: ps -ef | grep java (先查java进程ID) kill -9  PID(生产环境谨慎使用) kill.killall.pkill命令的区别 ...

  9. 异步Promise及Async/Await可能最完整入门攻略

    此文只介绍Async/Await与Promise基础知识与实际用到注意的问题,将通过很多代码实例进行说明,两个实例代码是setDelay和setDelaySecond. tips:本文系原创转自我的博 ...

  10. MySQL 的自增 ID 用完了,怎么办?

      一.简述 在 MySQL 中用很多类型的自增 ID,每个自增 ID 都设置了初始值.一般情况下初始值都是从 0 开始,然后按照一定的步长增加.在 MySQL 中只要定义了这个数的字节长度,那么就会 ...