幾個線段樹板子(區間加/區間加與乘)

一直仰慕dl能夠把線段樹玩出花來,所以就想手寫並整理一下幾個常見的線段樹板子(主要是結構化得好看一些)
Part Ⅰ區間加法+區間求和
洛谷P3372
基礎中的基礎

//luogu P3372 199ms
#include <bits/stdc++.h>
#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 <bits/stdc++.h>
#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 Ⅲ
未完待續

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章