题目传送: 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. 使用批处理命令注册运行mysql数据库,无需注册mysql服务,可以在任意电脑登录使用

    使用批处理命令初始化和开启mysql服务,移植数据库之后可以直接运行访问,对于学习数据库的人来说特别的方便哦. 我们可以从mysql官网下载官方社区版本的mysql: 这里使用之前下载的8.0.15来 ...

  2. CentOS7 内核模块管理

    1.查看所有模块:lsmod 2.查看指定模块的详细信息:modinfo 模块名 3.动态加载模块:modprobe 模块名 4.动态卸载模块:modprobe -r 模块名 5.开机自动加载模块:假 ...

  3. kotlin学习(8)泛型

    泛型的声明 与Java不同的是,Kotlin要求类型实参要么被显示的说明,要么能被编译器推导出来.因为Java在1.5版本才引入泛型,所以为了兼容过去的版本,可以不直接声明List类型的变量,而不说明 ...

  4. css控制文本内容显示省略号

    1,单行文字显示省略号 div{ width:200px; overflow:hideen; white-space:nowrap; text-overflow:ellipsis; } 2,多行文字显 ...

  5. xcode自动生成代码片段

    一.什么是代码片段 当在Xcode中输入dowhile并回车后,Xcode会出现下图所示的提示代码: 这就是代码片段,目的是使程序员以最快的速度输入常用的代码片段,提高编程效率.该功能是从Xcode4 ...

  6. python 变量、列表、元组、字典

    python 变量.列表.元组.字典 1.python 变量赋值 2.ptython  列表 3.python 元组 4.python  字典 1.  Python变量赋值     1.1变量的命名规 ...

  7. PAT Basic 1031 查验身份证 (15 分)

    一个合法的身份证号码由17位地区.日期编号和顺序编号加1位校验码组成.校验码的计算规则如下: 首先对前17位数字加权求和,权重分配为:{7,9,10,5,8,4,2,1,6,3,7,9,10,5,8, ...

  8. UVa 10294(polya 翻转与旋转)

    Arif in Dhaka(First Love Part 2) Input: standard input Output: standard output Time Limit: 2 seconds ...

  9. sshfs 挂载远程文件夹

    1 安装 # yum install sshfs # dnf + releases] $ sudo apt-get install sshfs [On Debian/Ubuntu based syst ...

  10. ztree 获取CheckBox选中节点时,不获取选中上级父节点

    //将第三个参数改为false,表示不去勾选父节点下的所有子节点 zTreeObj.checkNode(node, true, false); setting.check.chkboxType = { ...