作为一道调了三天的模板题,真的太虐心了对于理解线段树大有用处。
如题,已知一个数列,你需要进行下面三种操作:
1.将某区间每一个数乘上x
2.将某区间每一个数加上x
3.求出某区间每一个数的和
第一行包含三个整数N、M、P,分别表示该数列数字的个数、操作的总个数和模数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k
操作2: 格式:2 x y k 含义:将区间[x,y]内每个数加上k
操作3: 格式:3 x y 含义:输出区间[x,y]内每个数的和对P取模所得的结果
输出包含若干行整数,即为所有操作3的结果。
输出样例:
17
2
说明
时空限制:1000 ms , 128 M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=1000,M<=10000
对于100%的数据:N<=100000,M<=100000
样例说明:
故输出应为17、2(40 mod 38=2)
题解
欲知线段树是什么,请看前文 线段树1
同线段树1:
void bt(int l,int r)
{
len++;int now=len;
tr[now].l=l;tr[now].r=r;tr[now].lc=tr[now].rc=-1;tr[now].c=tr[now].lza=0;tr[now].lzm=1;
if(l
需下放两个标记:lza (加法),lzm (乘法),优先级为先乘后加。
下放乘法标记时直接让左右儿子的lzm都乘上父亲的lzm,而加法标记则需先乘一遍父亲的lzm再加上lza。
由于优先级,子树的值则先乘父亲的lzm再加上lza (掌管多少个叶子结点就加多少个)。
void pushdown(int now)
{
int lc=tr[now].lc,rc=tr[now].rc;
tr[lc].lzm=tr[lc].lzm*tr[now].lzm%p;
tr[rc].lzm=tr[rc].lzm*tr[now].lzm%p;
tr[lc].lza=(tr[lc].lza*tr[now].lzm+tr[now].lza)%p;
tr[rc].lza=(tr[rc].lza*tr[now].lzm+tr[now].lza)%p;
tr[lc].c=(tr[lc].c*tr[now].lzm+tr[now].lza*(tr[lc].r-tr[lc].l+1))%p;
tr[rc].c=(tr[rc].c*tr[now].lzm+tr[now].lza*(tr[rc].r-tr[rc].l+1))%p;
tr[now].lza=0;
tr[now].lzm=1;
}
加法修改同线段树1:
void add(int now,int l,int r,ll k)
{
if(tr[now].l==l && tr[now].r==r)
{
tr[now].lza=(tr[now].lza+k)%p;
tr[now].c=(tr[now].c+(r-l+1)*k)%p;
return;
}
pushdown(now);
int lc=tr[now].lc,rc=tr[now].rc;
int mid=(tr[now].l+tr[now].r)/2;
if(r<=mid) add(lc,l,r,k);
else if(mid+1<=l) add(rc,l,r,k);
else
{
add(lc,l,mid,k);
add(rc,mid+1,r,k);
}
tr[now].c=(tr[lc].c+tr[rc].c)%p;
}
进行乘法修改时,我们需要把目标区间的 lzm,lza 和目标区间的值都修改。并且每找到一棵子树就往下推一次 lazy:
void mult(int now,int l,int r,ll k)
{
if(tr[now].l==l && tr[now].r==r)
{
tr[now].lzm=tr[now].lzm*k%p;
tr[now].lza=tr[now].lza*k%p;
tr[now].c=tr[now].c*k%p;
return;
}
pushdown(now);
int lc=tr[now].lc,rc=tr[now].rc;
int mid=(tr[now].l+tr[now].r)/2;
if(r<=mid) mult(lc,l,r,k);
else if(mid+1<=l) mult(rc,l,r,k);
else
{
mult(lc,l,mid,k);
mult(rc,mid+1,r,k);
}
tr[now].c=(tr[lc].c+tr[rc].c)%p;
}
同线段树1:
ll findsum(int now,int l,int r)
{
if(tr[now].l==l && tr[now].r==r) return tr[now].c%p;
pushdown(now);
int lc=tr[now].lc,rc=tr[now].rc;
int mid=(tr[now].l+tr[now].r)/2;
if(r<=mid) return findsum(lc,l,r)%p;
else if(mid+1<=l) return findsum(rc,l,r)%p;
else return (findsum(lc,l,mid)+findsum(rc,mid+1,r))%p;
}
在每次修改相关的值时(包括 lza , lzm , 与节点本身的值)都对 p 取一次模。
Code:
#include
#include
#define ll long long
struct node{int l,r,lc,rc;ll c,lza,lzm;}tr[400010];
ll a[100010];
int n,m,len=0;
ll p;
void bt(int l,int r)
{
len++;int now=len;
tr[now].l=l;tr[now].r=r;tr[now].lc=tr[now].rc=-1;tr[now].c=tr[now].lza=0;tr[now].lzm=1;
if(l
(温馨提示:记得开 long long)