(模板)线段树(未完结)
(模板)线段树
单点修改 + 区间查询(无\(tag\))
神奇的代码
#define int long long
using i64 = long long;
const int maxn = 5e5 + 5;
int nums[maxn];
int tree[4 * maxn];
void update(int root)
{
int ch = root << 1;
tree[root] = tree[ch] + tree[ch + 1];
}
void build(int root, int left, int right)
{
if (left == right)
{
tree[root] = nums[left];
return;
}
int ch = root << 1;
int mid = (left + right) >> 1;
build(ch, left, mid);
build(ch + 1, mid + 1, right);
update(root);
}
void modify(int pos, int root, int left, int right, int x)
{
if (left == right)
{
tree[root] += x;
return;
}
int ch = root << 1;
int mid = (left + right) >> 1;
if (pos <= mid)
{
modify(pos, ch, left, mid, x);
}
else
{
modify(pos, ch + 1, mid + 1, right, x);
}
update(root);
}
int range_query(int root, int left, int right, int x, int y)
{
if (x <= left && right <= y)
{
return tree[root];
}
int ch = root << 1;
int mid = (left + right) >> 1;
if (y <= mid)
{
return range_query(ch, left, mid, x, y);
}
if(x > mid)
{
return range_query(ch + 1, mid + 1, right, x, y);
}
return range_query(ch, left, mid, x, mid) + range_query(ch + 1, mid + 1, right, mid + 1, y);
}
区间修改 + 单点查询 + 区间查询(\(tag\)标记)
需要这样几个函数
\(pushdown\): 把标记传下去
神奇的代码
void pushdown(int root, int left, int right) // 把父亲的标记传给儿子
{
int tot = right - left + 1;
if (lazy[root] != 0)
{
int ch = root << 1;
tree[ch] += (tot - tot / 2) * lazy[root];
tree[ch + 1] += (tot / 2) * lazy[root];
lazy[ch] += lazy[root];
lazy[ch + 1] += lazy[root];
lazy[root] = 0;
}
}
为何\(pushdown\)的时候要改变结点的值?
\(lazy\)标记: 在更新区间的时候,为了方便,只更新了这个完整的区间,它的子区间未被更新而是打上了标记,因此向下一层传标记的时候这一层要改变
\(update\):由儿子更新父亲
神奇的代码
void update(int root, int left, int right)
{
int ch = root << 1;
int mid = (left + right) >> 1;
pushdown(ch, left, mid);
pushdown(ch + 1, mid + 1, right);
tree[root] = tree[ch] + tree[ch + 1];
}
\(build\): 建树
神奇的代码
void build(int root, int left, int right)
{
if (left == right)
{
tree[root] = nums[left];
return;
}
int ch = root << 1;
int mid = (left + right) >> 1;
build(ch, left, mid);
build(ch + 1, mid + 1, right);
update(root, left, right);
}
\(range\_modify\): 区间修改
神奇的代码
void range_modify(int root, int left, int right, int x, int y, int val)
{
pushdown(root, left, right);
if (x <= left && right <= y)
{
lazy[root] += val;
tree[root] += (right - left + 1) * val;
return;
}
int ch = root << 1;
int mid = (left + right) >> 1;
if (y <= mid)
{
range_modify(ch, left, mid, x, y, val);
}
else if (x > mid)
{
range_modify(ch + 1, mid + 1, right, x, y, val);
}
else
{
range_modify(ch, left, mid, x, mid, val);
range_modify(ch + 1, mid + 1, right, mid + 1, y, val);
}
update(root, left, right);
}
注意:这里区间修改为什么要\(pushdown\)呢,查询的时候直接\(pushdown\)不就行了吗? 有可能有这样的情况
-
- \(+3\)
-
- 变为\(x\)
和这样的情况
-
- 变为\(x\)
-
- \(+3\)
就会出问题了。当\(tag\)具有一定的时序关系的时候一定要在修改的时候\(pushdown\)一下
\(range\_query\): 区间查询
神奇的代码
int range_query(int root, int left, int right, int x, int y)
{
pushdown(root, left, right);
if (x <= left && right <= y)
{
return tree[root];
}
int ch = root << 1;
int mid = (left + right) >> 1;
if (y <= mid)
{
return range_query(ch, left, mid, x, y);
}
if(x > mid)
{
return range_query(ch + 1, mid + 1, right, x, y);
}
return range_query(ch, left, mid, x, mid) + range_query(ch + 1, mid + 1, right, mid + 1, y);
}
区间修改 + 区间查询(多\(tag\))
这里是加乘线段树
对于多\(tag\),要有面向对象的思想,将\(tag\)也看成一个对象
需要这么几个函数
\(init\_lazy\): 初始化标记
神奇的代码
// 初始化lazy标记
void init_lazy(int root)
{
multi[root] = 1;
plus[root] = 0;
}
\(unionj\_lazy\)合并标记
神奇的代码
// 合并标记
void union_lazy(int root, int ch)
{
int tmp1 = multi[root] * multi[ch];
int tmp2 = multi[root] * plus[ch] + plus[root];
multi[ch] = tmp1;
plus[ch] = tmp2;
}
\(cal\_lazy\)计算标记
神奇的代码
// 计算标记
void cal_lazy(int root, int left, int right)
{
int tot = right - left + 1;
tree[root] = multi[root] * tree[root] + tot * plus[root];
}
不要犯傻 : 这里就乘了一次是因为乘法分配律
接下来其余的函数和以前一样,但是有些小变化
\(pushdown\):传标记
神奇的代码
void pushdown(int root, int left, int right) // 把父亲的标记传给儿子
{
if (plus[root] != 0 || multi[root] != 1)
{
cal_lazy(root, left, right);
int ch = root << 1;
union_lazy(root, ch);
union_lazy(root, ch + 1);
init_lazy(root);
}
}
\(update\):用子区间更新父亲区间
\(update\)变化不大
神奇的代码
void update(int root, int left, int right)
{
int ch = root << 1;
int mid = (left + right) >> 1;
pushdown(ch, left, mid);
pushdown(ch + 1, mid + 1, right);
tree[root] = tree[ch] + tree[ch + 1];
}
\(build\): 建树
这个也没啥变化
神奇的代码
void build(int root, int left, int right)
{
init_lazy(root);
if (left == right)
{
tree[root] = nums[left];
return;
}
int ch = root << 1;
int mid = (left + right) >> 1;
build(ch, left, mid);
build(ch + 1, mid + 1, right);
update(root, left, right);
}
\(range\_modify\) 区间修改
神奇的代码
void range_modify(int root, int left, int right, int x, int y, int val, int op)
{
pushdown(root, left, right);
if (x <= left && right <= y)
{
if (op == 1) // 乘法标记
{
multi[root] = val;
}
else
{
plus[root] = val;
}
return;
}
int ch = root << 1;
int mid = (left + right) >> 1;
if (y <= mid)
{
range_modify(ch, left, mid, x, y, val, op);
}
else if (x > mid)
{
range_modify(ch + 1, mid + 1, right, x, y, val, op);
}
else
{
range_modify(ch, left, mid, x, mid, val, op);
range_modify(ch + 1, mid + 1, right, mid + 1, y, val, op);
}
update(root, left, right);
}
\(range\_query\) 区间查询
神奇的代码
int range_query(int root, int left, int right, int x, int y)
{
pushdown(root, left, right);
if (x <= left && right <= y)
{
return tree[root] % mod;
}
int ch = root << 1;
int mid = (left + right) >> 1;
if (y <= mid)
{
return range_query(ch, left, mid, x, y) % mod;
}
if(x > mid)
{
return range_query(ch + 1, mid + 1, right, x, y) % mod;
}
return ((range_query(ch, left, mid, x, mid) % mod) + (range_query(ch + 1, mid + 1, right, mid + 1, y) % mod)) % mod;
}