几个线段树板子(区间加/区间加与乘)

一直仰慕dl能够把线段树玩出花来,所以就想手写并整理一下几个常见的线段树板子(主要是结构化得好看一些)
Part Ⅰ区间加法+区间求和
洛谷P3372
基础中的基础

//luogu P3372 199ms
#include 
#define ll long long
using namespace std;

const ll MAXN=1e5+10;
ll a[MAXN],tree[MAXN<<2],add[MAXN<<2],n,m,tt,x,y,k;
//a[]是原数组,tree[]是线段树,add[]是加操作的lazytag 

inline ll read()
//读入优化 
{
    ll res = 0;
    ll sym = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') sym = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        res = res * 10 + ch - '0';
        ch = getchar();
    }
    return res * sym;
}

inline ll lc(ll p)
{
	return p<<1;
}

inline ll rc(ll p)
{
	return p<<1|1;
}

inline void push_up(ll p)
//将处理好的左右孩子节点返给父亲 
{
	tree[p] = tree[lc(p)] + tree[rc(p)]; 
}

inline void build(ll p, ll l, ll r)
//l,r代表tree[p]对应的区间 
{
	if (l == r)
	{
		tree[p] = a[l];  //叶子结点
		return;
	}
	ll mid = (l+r) >> 1;
	build(lc(p), l, mid);
	build(rc(p), mid+1, r);
	push_up(p); //收集后回溯 
}
	
inline void work_add(ll p, ll l, ll r, ll k)
//给tree[p]增加k 
{
	add[p] += k;
	tree[p] += (r-l+1)*k;
}

inline void push_down(ll p, ll l, ll r)
//将tree[p]的lazytag下放 
{
	if (add[p] == 0) return; //无操作则剪枝 
	ll mid = (l + r) >> 1;
	work_add(lc(p), l, mid, add[p]);
	work_add(rc(p), mid+1, r, add[p]);
	add[p] = 0;
}

inline void update(ll ul, ll ur, ll p, ll l, ll r, ll k)
//ul,ur代表需要update的区间,l,r代表tree[p]对应的区间,k是加的值 
{
	if (ul <= l && r <= ur)   
	{
		work_add(p, l, r, k);
		return;
	}
	ll mid = (l+r)>>1;
	push_down(p, l, r);   //确保进入下面递归的tree[p]是正确的 
	if (mid >= ul) update(ul, ur, lc(p), l, mid, k);
	if (mid < ur) update(ul, ur, rc(p), mid+1, r, k);
	push_up(p);    //将子树的数据合并到父节点 
}

inline ll query(ll ql, ll qr, ll p, ll l, ll r)
//ql,qr代表query的区间,l,r代表tree[p]对应的区间 
{
	if (ql <= l && r <= qr) return tree[p];
	ll res = 0, mid = (l + r) >> 1;
	push_down(p, l, r);    //确保进入下面递归的tree[p]的值是正确的 
	if (mid >= ql) res += query(ql, qr, lc(p), l, mid);
	if (qr > mid) res += query(ql, qr, rc(p), mid+1, r);
	return res;
}


int main()
{
	cin>>n>>m;
	for (register ll i = 1; i <= n; i++) a[i] = read();
	build(1, 1, n);
	for (register ll i = 1; i <= m; i++)
	{
		tt = read();
		x = read();
		y = read();
		switch (tt)
		{
			case 1:
			{
				k = read();
				update(x, y, 1, 1, n, k);
				break;
			}
			case 2:
			{
				printf("%lld\n", query(x, y, 1, 1, n));
				break;
			}
		}
	}				
	return 0;
}

Part Ⅱ 区间加法+区间乘法+区间求和
容易证明,如果push_down先加再乘的话在update_add的过程中不得不把mul[p]改成浮点数,吃力不讨好
所以整体思路就是先乘再加,然后把各个重要操作认真地改写就行了

//luogu P3373 983ms
//原题要求对PP取模,易知取模对于乘和加是线性的 
//tree[p](real) = tree[p]*mul[p]+add[p];
//加:add[p] = add[p]+k;
//乘:add[p] = add[p]*k; mul[p] =  mul[p]*k;
#include 
#define ll long long
using namespace std;

const ll MAXN=1e5+10;
ll a[MAXN],tree[MAXN<<2],add[MAXN<<2],mul[MAXN<<2],n,m,tt,x,y,k,PP;
//a[]是原数组,tree[]是线段树,add[]是加操作的lazytag,mul[]是乘操作的lazytag 

inline ll read()
//读入优化 
{
    ll res = 0;
    ll sym = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') sym = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        res = res * 10 + ch - '0';
        ch = getchar();
    }
    return res * sym;
}

inline ll lc(ll p)
{
	return p<<1;
}

inline ll rc(ll p)
{
	return p<<1|1;
}

inline void push_up(ll p)
//将处理好的左右孩子节点返给父亲 
{
	tree[p] = (tree[lc(p)] + tree[rc(p)])%PP; 
}

inline void build(ll p, ll l, ll r)
//l,r代表tree[p]对应的区间 
{
	add[p]=0;
	mul[p]=1;
	if (l == r)
	{
		tree[p] = a[l];  //叶子结点
		return;
	}
	ll mid = (l+r) >> 1;
	build(lc(p), l, mid);
	build(rc(p), mid+1, r);
	push_up(p); //收集后回溯 
}
	
inline void work_add(ll p, ll l, ll r, ll k)
//给tree[p]增加k 
{
	add[p] += k;
	tree[p] = (tree[p]+(r-l+1)*k)%PP;
}

inline void work_mul(ll p, ll l, ll r, ll k)
//给tree[p]乘以k 
{
	add[p] = add[p]*k%PP;
	mul[p] = mul[p]*k%PP;
	tree[p] = tree[p]*k%PP;
}

inline void push_down(ll p, ll l, ll r)
//将tree[p]的lazytag下放,先乘再加 
{
	ll mid = (l + r) >> 1;
	if (mul[p] != 0)
	{
		work_mul(lc(p), l, mid, mul[p]);
		work_mul(rc(p), mid+1, r, mul[p]);
		mul[p]=1;
	}
	if (add[p] != 0)
	{
		work_add(lc(p), l, mid, add[p]);
		work_add(rc(p), mid+1, r, mul[p]);
		add[p]=0;
	}
}

inline void update_add(ll ul, ll ur, ll p, ll l, ll r, ll k)
//ul,ur代表需要update的区间,l,r代表tree[p]对应的区间,k是加的值 
{
	if (ul <= l && r <= ur)   
	{
		work_add(p, l, r, k);
		return;
	}
	ll mid = (l+r)>>1;
	push_down(p, l, r);   //确保进入下面递归的tree[p]是正确的 
	if (mid >= ul) update_add(ul, ur, lc(p), l, mid, k);
	if (mid < ur) update_add(ul, ur, rc(p), mid+1, r, k);
	push_up(p);    //将子树的数据合并到父节点 
}

inline void update_mul(ll ul, ll ur, ll p, ll l, ll r, ll k)
//ul,ur代表需要update的区间,l,r代表tree[p]对应的区间,k是乘的值 
{
	if (ul <= l && r <= ur)   
	{
		work_mul(p, l, r, k);
		return;
	}
	ll mid = (l+r)>>1;
	push_down(p, l, r);   //确保进入下面递归的tree[p]是正确的 
	if (mid >= ul) update_mul(ul, ur, lc(p), l, mid, k);
	if (mid < ur) update_mul(ul, ur, rc(p), mid+1, r, k);
	push_up(p);    //将子树的数据合并到父节点 
}

inline ll query(ll ql, ll qr, ll p, ll l, ll r)
//ql,qr代表query的区间,l,r代表tree[p]对应的区间 
{
	if (ql <= l && r <= qr) return tree[p];
	ll res = 0, mid = (l + r) >> 1;
	push_down(p, l, r);    //确保进入下面递归的tree[p]的值是正确的 
	if (mid >= ql) res = (query(ql, qr, lc(p), l, mid))%PP;
	if (qr > mid) res = (res+query(ql, qr, rc(p), mid+1, r))%PP;
	return res%PP;
}


int main()
{
	cin>>n>>m>>PP;
	for (register ll i = 1; i <= n; i++) a[i] = read();
	build(1, 1, n);
	for (register ll i = 1; i <= m; i++)
	{
		tt = read();
		x = read();
		y = read();
		switch (tt)
		{
			case 1:
			{
				k = read();
				update_mul(x, y, 1, 1, n, k);
				break;
			}
			case 2:
			{
				k = read();
				update_add(x, y, 1, 1, n, k);
				break;				
			}
			case 3:
			{
				printf("%lld\n", query(x, y, 1, 1, n));
				break;
			}
		}
	}				
	return 0;
}

Part Ⅲ
未完待续

你可能感兴趣的:(线段树,数据结构)