线段树模板(维护加法和乘法 c++实现)

本章主要总结线段树的模板,有关于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);
}

posted @ 2023-01-28 11:16  hugeYlh  阅读(27)  评论(0编辑  收藏  举报  来源