洛谷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 一个表示加的 一个表示乘的 需要先计算乘法 再计算加法 来自你谷 ...
随机推荐
- C#中的委托和事件(一)——delegate
前言 来说一说委托(delegate)和事件(event),本篇采取的形式是翻译微软Delegate的docs中的重要部分(不要问我为什么微软的docs有中文还要读英文,因为读中文感觉自己有阅读障碍- ...
- Centos7 yum安装LNMP
1.Centos7系统库中默认是没有nginx的rpn包的,所以我们需要先更新下rpm依赖库 (1):使用yum安装nginx,安装nginx库 rpm -Uvh http://nginx.org/p ...
- HNUSTOJ-1051 最长的波动序列
1051: 最长的波动序列 时间限制: 1 Sec 内存限制: 128 MB提交: 47 解决: 13[提交][状态][讨论版] 题目描述 有一个长度为N的整数序列,序列里面的数是两两不同的,现在 ...
- 理解 JavaScript 闭包
这是本系列的第 4 篇文章. 作为 JS 初学者,第一次接触闭包的概念是因为写出了类似下面的代码: for (var i = 0; i < helpText.length; i++) { var ...
- 2019牛客暑期多校训练营(第八场) - B - Beauty Values - 水题
https://ac.nowcoder.com/acm/contest/888/B 实际上的确是个水题,写个小数据找个规律看看,所谓不同度,其实就是依次插入每个元素后,各种元素出现的最后位置的坐标求和 ...
- spring boot 枚举使用的坑3
上一篇说到spring boot 使用jackson在枚举enum序列化和反序列化的问题, 再来说说在JPA中实体entity使用枚举的问题. 还是这个枚举: @Getter @AllArgsCons ...
- CSS中的伪类和为伪元素
伪类: 伪元素:
- MySQL事务提交与回滚
提交 为了演示效果,需要打开两个终端窗口,使用同一个数据库,操作同一张表 step1:连接 终端1:查询商品分类信息 select * from goods_cates; step2:增加数据 终端2 ...
- L3-002 特殊堆栈 (30 分)
大家都知道“堆栈”是一种“先进后出”的线性结构,基本操作有“入栈”(将新元素插入栈顶)和“出栈”(将栈顶元素的值返回并从堆栈中将其删除).现请你实现一种特殊的堆栈,它多了一种操作叫“查中值”,即返回堆 ...
- 10年前文章_fedora10root登录
fedora10系统root用户登录以及普通用户使用sudo命令1.问题:fedora10默认不能够直接用root用户登录解决方法:我们可以先以普通用户登录$su#vi /etc/pam.d/gdm删 ...