文章目录

  • ​​push_down函数​​
  • ​​区间更新​​
  • ​​区间查询​​
  • ​​模板代码​​

本章主要总结线段树的模板,有关于lazytag标记与线段树的维护乘法和加法的模板


我们用tag这个结构体分别表示add和mul标记,tree存储区间和

int p;  //余数
const int N=1e5+10;
int tree[N<<2]; //区间和
struct tag
{
int add,mul;
}tag[N<<2]

push_down函数

对于孩子节点的标记,我们统称为孩子节点的… 例如孩子节点的乘法标记 …
对于父亲节点的标记,我们省略,例如乘法标记,加法标记,表示父亲节点的。

  1. 处理加法标记:孩子节点加法标记 * 乘法标记 + 加法标记
  2. 处理乘法标记:孩子节点乘法标记 * 乘法标记
  3. 处理区间和:孩子节点区间和 * 乘法标记 + 加法标记 * 区间长度
inline void settag(int i,int pl,int pr,int multag,int addtag)
{
tag[i].mul=(tag[i].mul*multag)%p; //乘法标记
tag[i].add=(tag[i].add*multag%p+addtag)%p; //加法标记
tree[i]=(tree[i]*multag%p+addtag*(pr-pl+1)%p)%p;//区间和
}

同时对于push_down函数,我们也要进行相应的处理,注意加法标记清零,乘法标记置1

inline void push_down(int i,int pl,int pr)
{
//加法标记:
int mid=(pl+pr)>>1;
settag(ls(i),pl,mid,tag[i].mul,tag[i].add); //处理左孩子
settag(rs(i),mid+1,pr,tag[i].mul,tag[i].add);//处理右孩子
//原始标记清除
tag[i].add=0;
tag[i].mul=1;
return;
}

区间更新

区间的更新涉及update函数,同时对于加法与乘法的update函数它们添加标记的方式也有不同。

对于维护加法的添加标记:区间内的每个数都加上k

  1. 加法标记加上k
  2. 区间和加上 k*区间长度
void addtag(int i,int pl,int pr)
{
tag[i].add=(tag[i].add+k)%mod;
tree[i]=(tree[i]+k*(pr-pl+1)%mod)%mod;
}

对于维护乘法的添加标记:区间的每个数都乘以k

  1. 乘法标记乘k
  2. 加法标记乘k
  3. 区间和乘k
void multag(int i,int pl,int pr)
{
tag[i].mul=tag[i].mul*k%p;
tag[i].add=tag[i].add*k%p;
tree[i]=tree[i]*k%p;
}

update函数其他写法与正常的update函数一致:

//加法更新线段树
void update_add(int i,int pl,int pr,int L,int R,int k)
{
if (L<=pl && pr<=R)
{
//子区间被完全覆盖时,这是个待修改的区间,添加标记
tree[i]=(tree[i]+k*(pr-pl+1)%p)%p; //区间和+k*区间长度
tag[i].add=(tag[i].add+k)%p; //加法标记+k
return;
}
push_down(i,pl,pr); //标记下移
int mid=(pl+pr)>>1;
if (L<=mid) update_add(ls(i),pl,mid,L,R,k);
if (R>mid) update_add(rs(i),mid+1,pr,L,R,k);
push_up(i); //标记上移
}
//乘法更新线段树
void update_mul(int i,int pl,int pr,int L,int R,int k)
{
if (L<=pl && pr<=R)
{
//子区间完全被覆盖,标记乘法
tag[i].mul=tag[i].mul*k%p;
tag[i].add=tag[i].add*k%p;
tree[i]=tree[i]*k%p;
return;
}
push_down(i,pl,pr);
int mid=(pl+pr)>>1;
if (L<=mid) update_mul(ls(i),pl,mid,L,R,k);
if (R>mid) update_mul(rs(i),mid+1,pr,L,R,k);
push_up(i);
}

区间查询

我们的区间查询函数与普通的查询函数基本一致:

//查询区间和
int query(int i,int pl,int pr,int L,int R)
{
if (L<=pl && pr<=R)
{
return tree[i];
}
int res=0;
push_down(i,pl,pr); //下移标记
int mid=(pl+pr)>>1;
if (L<=mid) res+=query(ls(i),pl,mid,L,R);
if (R>mid) res+=query(rs(i),mid+1,pr,L,R);
return res%p;
}

需要注意的地方

  1. 在处理区间乘法的时候,我们要注意初始化乘法标记为1,加法标记为0
  2. 我们最好在处理乘法的时候始终使用 long long 的数据类型
  3. 注意数组区间开四倍
  4. 不要忘记 取余对于大数的运算多取余肯定没错

模板代码

另外附带在OJ模式下我常用的模板,可以参考一下

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using DB = double;
using PI = pair<int, int>;
using PL = pair<LL, LL>;
template<typename T> using v = vector<T>;
constexpr auto INF = 0X3F3F3F3F;
template<typename T1,typename T2> using umap = unordered_map<T1, T2>;
#define ic std::ios::sync_with_stdio(false);std::cin.tie(nullptr)
template <typename ConTainermap> void dbgumap(ConTainermap c); //output umap
#if 1
#define int LL
#endif
inline int read(); //fast input
inline void write(int x); //fast output

//TODO: Write code here
int n,m,p;
const int N=1e5+10;
int nums[N<<1];
int tree[N<<2]; //存储和
struct tag
{
int add,mul; //乘法和加法标记
}tag[N<<2];
inline int ls(int i)
{
return i<<1;
}
inline int rs(int i)
{
return i<<1|1;
}
inline void push_up(int i)
{
//求区间和
tree[i]=(tree[ls(i)]+tree[rs(i)])%p;
}
inline void settag(int i,int pl,int pr,int multag,int addtag)
{
tag[i].mul=(tag[i].mul*multag)%p; //乘法标记
tag[i].add=(tag[i].add*multag%p+addtag)%p; //加法标记
tree[i]=(tree[i]*multag%p+addtag*(pr-pl+1)%p)%p;//区间和
}
inline void push_down(int i,int pl,int pr)
{
//加法标记:
int mid=(pl+pr)>>1;
settag(ls(i),pl,mid,tag[i].mul,tag[i].add);
settag(rs(i),mid+1,pr,tag[i].mul,tag[i].add);
//原始标记清除
tag[i].add=0;
tag[i].mul=1;
return;
}
//创建线段树
void build(int i,int pl,int pr)
{
if (pl==pr)
{
tree[i]=nums[pl];
return;
}
int mid=(pl+pr)>>1;
build(ls(i),pl,mid); //递归左孩子
build(rs(i),mid+1,pr); //递归右孩子
push_up(i); //自底向上传递到父节点
}
//加法更新线段树
void update_add(int i,int pl,int pr,int L,int R,int k)
{
if (L<=pl && pr<=R)
{
//子区间被完全覆盖时,这是个待修改的区间,添加标记
tree[i]=(tree[i]+k*(pr-pl+1)%p)%p; //区间和+k*区间长度
tag[i].add=(tag[i].add+k)%p; //加法标记+k
return;
}
push_down(i,pl,pr); //标记下移
int mid=(pl+pr)>>1;
if (L<=mid) update_add(ls(i),pl,mid,L,R,k);
if (R>mid) update_add(rs(i),mid+1,pr,L,R,k);
push_up(i); //标记上移
}
//乘法更新线段树
void update_mul(int i,int pl,int pr,int L,int R,int k)
{
if (L<=pl && pr<=R)
{
//子区间完全被覆盖,标记乘法
tag[i].mul=tag[i].mul*k%p;
tag[i].add=tag[i].add*k%p;
tree[i]=tree[i]*k%p;
return;
}
push_down(i,pl,pr);
int mid=(pl+pr)>>1;
if (L<=mid) update_mul(ls(i),pl,mid,L,R,k);
if (R>mid) update_mul(rs(i),mid+1,pr,L,R,k);
push_up(i);
}
//查询区间和
int query(int i,int pl,int pr,int L,int R)
{
if (L<=pl && pr<=R)
{
return tree[i];
}
int res=0;
push_down(i,pl,pr); //下移标记
int mid=(pl+pr)>>1;
if (L<=mid) res+=query(ls(i),pl,mid,L,R);
if (R>mid) res+=query(rs(i),mid+1,pr,L,R);
return res%p;
}
signed main()
{
cin>>n>>m>>p;
for (int i=1;i<=n;i++)
{
tag[i].mul=1;
}
for(int i=1;i<=n;i++)
{
scanf("%lld",&nums[i]);
}
build(1,1,n);
for (int i=1;i<=m;i++)
{
int w,l,r,k;
scanf("%lld",&w);
if (w==1)
{
//区间每个数乘k
scanf("%lld%lld%lld",&l,&r,&k);
update_mul(1,1,n,l,r,k);
}
else if (w==2)
{
//区间每个数加k
scanf("%lld%lld%lld",&l,&r,&k);
update_add(1,1,n,l,r,k);
}
else if (w==3)
{
//查询区间和
scanf("%lld%lld",&l,&r);
printf("%lld\n",query(1,1,n,l,r)%p);
}
}
return 0;
}
template <typename ConTainermap>
void dbgumap(ConTainermap c)
{
for (auto& x:c)
{
cout<<"key:"<<x.first<<" val:"<<x.second<<endl;
}
}
inline int read()
{
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9')
{
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
inline void output(int x)
{
static int sta[35];
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
while (top) putchar(sta[--top] + 48);
}