洛谷P3373 【模板】线段树 2 && P2023 [AHOI2009]维护序列——题解
题目传送: 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]维护序列——题解的更多相关文章
- 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)
To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...
- 洛谷 P2023 [AHOI2009]维护序列 题解
P2023 [AHOI2009]维护序列 题目描述 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,-,aN .有如下三种操作形式: (1)把数列中 ...
- P2023 [AHOI2009]维护序列 题解(线段树)
题目链接 P2023 [AHOI2009]维护序列 解题思路 线段树板子.不难,但是...有坑.坑有多深?一页\(WA\). 由于乘法可能乘\(k=0\),我这种做法可能会使结果产生负数.于是就有了这 ...
- 【线段树】Bzoj1798 [AHOI2009] 维护序列
Description 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中的一段数全部乘一个值; (2 ...
- 洛谷P2023 [AHOI2009]维护序列(线段树区间更新,区间查询)
洛谷P2023 [AHOI2009]维护序列 区间修改 当我们要修改一个区间时,要保证 \(ax+b\) 的形式,即先乘后加的形式.当将区间乘以一个数 \(k\) 时,原来的区间和为 \(ax+b\) ...
- 洛谷 P2023 [AHOI2009]维护序列
P2023 [AHOI2009]维护序列 题目描述 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,…,aN .有如下三种操作形式: (1)把数列中 ...
- [洛谷P2023] [AHOI2009]维护序列
洛谷题目链接:[AHOI2009]维护序列 题目描述 老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成. 有长为N的数列,不妨设为a1,a2,-,aN .有如下三种操作形式: (1)把数列 ...
- 线段树_区间加乘(洛谷P3373模板)
题目描述 如题,已知一个数列,你需要进行下面三种操作: 1.将某区间每一个数乘上x 2.将某区间每一个数加上x 3.求出某区间每一个数的和 输入格式: 第一行包含三个整数N.M.P,分别表示该数列数字 ...
- 【题解】洛谷P2023 [AHOI2009] 维护序列(线段树)
洛谷P2023:https://www.luogu.org/problemnew/show/P2023 思路 需要2个Lazy-Tag 一个表示加的 一个表示乘的 需要先计算乘法 再计算加法 来自你谷 ...
随机推荐
- 使用Dockerfile制作镜像
组成部分 基础镜像信息 FROM 维护者信息 MAINTAINER.LABEL 镜像操作指令 RUN.COPY.ADD.EXPOSE.WORKDIR.ONBUILD.US ...
- MySQL的事务和视图
事务 1.概念 一条或者多条sql语句的集合! 事务:就是一堆操作的集合,他们同生共死.要么都执行成功,要么都执行失败2.事务的特性 ACID A:原子性 完整的,不可分割的 原子性 (Atom ...
- 【Luogu P2201】【JZOJ 3922】数列编辑器
题面: Description 小Z是一个爱好数学的小学生.最近,他在研究一些关于整数数列的性质. 为了方便他的研究,小Z希望实现一个叫做"Open Continuous Lines Pro ...
- Thinkphp设置PC和手机端模板
<?php // 判断手机端 function ismobile() { // 如果有HTTP_X_WAP_PROFILE则一定是移动设备 if (isset ($_SERVER['HTTP_X ...
- kotlin学习(10)反射
反射,简单来说,是一种在运行时动态地访问对象属性和方法的方式,而不需要事先确定这些属性是什么. Kotlin反射API:KClass.KCallable.KFunction.KPropperty KC ...
- POJ 3259 Wormholes SPFA算法题解
版权声明:本文作者靖心,靖空间地址:http://blog.csdn.net/kenden23/,未经本作者同意不得转载. https://blog.csdn.net/kenden23/article ...
- python Rabbitmq编程(一)
python Rabbitmq编程(一) 实现最简单的队列通信 send端 #!/usr/bin/env python import pika credentials = pika.PlainCred ...
- Python numpy数据的保存和读取
在科学计算的过程中,往往需要保存一些数据,也经常需要把保存的这些数据加载到程序中,在 Matlab 中我们可以用 save 和 lood 函数很方便的实现.类似的在 Python 中,我们可以用 nu ...
- mac 命令行终端 设置代理
环境: macOS Mojave 10.14.3 iTrem 2 3.2.8 酸酸乳1.1.4.4-R8 查看自己命令行的状态 curl ip.gs 正式开始 一.首先检查自己的酸酸乳是否正常,并在高 ...
- Nginx 编译安装工程优化
1.减小 nginx 编译后的文件大小 在编译 nginx 时,默认以 debug 模式进行,在 debug 模式下会插入很多跟踪和 assert 之类的信息. 在 nginx 源码文件解压后,找到源 ...