一直仰慕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 Ⅲ
未完待續