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