区间修改 & 查询
文章目录
树状数组
优势:单点修改,区间查询
时间复杂度: O ( log n ) O (\log_n) O(logn)
存储原理
- A:原数组
- C:构造数组
- 对于每一个
C [i]
,共存 2 k 2^k 2k个元素,k
为i
二进制下末尾0的个数
C [ 1 ( 1 ) ] = A [ 1 ] C [ 2 ( 10 ) ] = A [ 1 ] + A [ 2 ] C [ 3 ( 11 ) ] = A [ 3 ] C [ 4 ( 100 ) ] = A [ 1 ] + A [ 2 ] + A [ 3 ] + A [ 4 ] C [ 5 ( 101 ) ] = A [ 5 ] C [ 6 ( 110 ) ] = A [ 5 ] + A [ 6 ] C [ 7 ( 111 ) ] = A [ 7 ] C [ 8 ( 1000 ) ] = A [ 1 ] + A [ 2 ] + A [ 3 ] + A [ 4 ] + A [ 5 ] + A [ 6 ] + A [ 7 ] + A [ 8 ] … … C [ i ] = A [ i − 2 k + 1 ] + A [ i − 2 k + 2 ] + … … + A [ i ] C [1 \ (1)] = A [1] \ \\ C [2 \ (10)] = A [1] + A [2] \\ C [3 \ (11)] = A [3] \\ C [4 \ (100)] = A [1] + A [2] + A [3] + A [4] \\ C [5 \ (101)] = A [5] \\ C [6 \ (110)] = A [5] + A [6] \\ C [7 \ (111)] = A [7] \\ C [8 \ (1000)] = A [1] + A [2] + A [3] + A [4] + A [5] + A [6] + A [7] + A [8] \\ …… \\ C [i] = A [i - 2 ^ k + 1] + A [i - 2 ^ k + 2] + …… + A [i] C[1 (1)]=A[1] C[2 (10)]=A[1]+A[2]C[3 (11)]=A[3]C[4 (100)]=A[1]+A[2]+A[3]+A[4]C[5 (101)]=A[5]C[6 (110)]=A[5]+A[6]C[7 (111)]=A[7]C[8 (1000)]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8]……C[i]=A[i−2k+1]+A[i−2k+2]+……+A[i]
2 k 2 ^ k 2k求值(lowbit)
2 k = i & ( − i ) 2 ^ k = i \ \& \ (-i) 2k=i & (−i)
- x = 0, 0 & 0 = 0
- x 为奇数,x & (-x) = 1
- x 为偶数,且恰好为 2 n 2^n 2n,则x & (-x) = x
- x 为偶数,但不为 2 n 2 ^ n 2n,则x = y * (2 ^ k) 。本质是把x用一奇数左移k位,因此x & (-x) = 2 ^ k
- x 为偶数,结果为x中2的最大次方的因子,即lowbit,取2 ^ k
Code
单点修改
// x点加上v,维护C
void ADD (int x,int v)
{
while (x <= n)
{
c [x] += v;
x += lowbit (x);
}
}
// 构造
for (int i = 1;i <= n;i ++)
{
cin >> data;
ADD (i,data);
}
区间查询
// 查询[1,x]和
int SUM (int x)
{
int res;
while (x)
{
res += c [x];
x -= lowbit (x);
}
return res;
}
// 查询[l,r]和
res = SUM (r) - SUM (l - 1);
例题
P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
线段树
优势:区间修改,单点查询
时间复杂度: log n \log_n logn
储存原理
- 二叉搜索树
tr [i].sum = tr [i * 2],sum + tr [i * 2 + 1].sum
结点 = 左子树 + 右子树- n >= 3时[1,n]的线段树可将[1,n]的任意子区间[L,R]分解为不超过 2 [ log 2 ( n − 1 ) ] 2[\log_2(n - 1)] 2[log2(n−1)]个子区间
Code
结构
struct TREE
{
int l,r,dat; // 左,右,和
int lz; // lazytag
};
TREE tr [10001];
// 左子树
inline int TL(long long x)
{
return x << 1;
}
// 右子树
inline int TR(long long x)
{
return x << 1 | 1;
}
构造
// 起始点,左,右
void Build(long long i, long long l, long long r)
{
tr[i].l = l;
tr[i].r = r;
if (l == r) // 为叶子节点,直接赋值
{
tr[i].dat = a[l];
return;
}
long long mid = (l + r) >> 1;
// Left
Build(TL(i), l, mid);
// Right
Build(TR(i), mid + 1, r);
tr[i].dat = tr[TL(i)].dat + tr[TR(i)].dat;
}
PushDown
- 快速传递数据下去
void PushDown(long long i)
{
if (tr[i].lz)
{
tr[TL(i)].lz += tr[i].lz;
tr[TR(i)].lz += tr[i].lz;
tr[TL(i)].dat += tr[i].lz * (tr[TL(i)].r - tr[TL(i)].l + 1);
tr[TR(i)].dat += tr[i].lz * (tr[TR(i)].r - tr[TR(i)].l + 1);
tr[i].lz = 0;
}
}
区间修改
void Add (int i, int l, int r, int val)
{
if (tr[i].l >= l && tr[i].r <= r)
{
tr[i].dat += val * (tr[i].r - tr[i].l + 1);
tr[i].lz += val;
return;
}
PushDown(i);
long long mid = (tr[i].l + tr[i].r) >> 1;
if (l <= mid) Add(TL(i), l, r, val);
if (r > mid) Add(TR(i), l, r, val);
tr[i].dat = tr[TL(i)].dat + tr[TR(i)].dat;
}
单点查询
int Find(int i, int pos)
{
if (tr[i].l == pos && tr[i].r == pos)
return tr[i].dat;
PushDown(i);
long long mid = (tr[i].l + tr[i].r) >> 1;
if (pos <= mid) return Find(TL(i), pos);
if (pos > mid) return Find(TR(i), pos);
}
例题
https://www.luogu.com.cn/problem/P3368
ST表
优势:区间求最值(RMQ)
储存原理
- 倍增思想
f [i][j]
表示从i
位置开始的 2 j 2^j 2j个数中的最大值- 转移时可把点股权区间拆解成两个区间并分别取最大值:
f [i] [j] = max (f [i] [j - 1], f [i + 2 ^ (j - 1)] [j - 1]);
- 设原数组为
a [N]
,开辟的f [N][31]
表示从i位置开始的 2 j 2^j 2j个数中的最大值 f [i][0]
表示元素本身,可初始化为f [i][0] = a [i]
Code
构造
void build ()
{
// 直接赋值
for (int i = 1;i <= n;i ++)
f [i][0] = a [i];
for (int j = 1;j <= 30;j ++)
for (int i = 1;i + (1 << j) - 1 <= n;i ++)
f [i][j] = max (f [i][j - 1],f [i + (1 << (j - 1))][j - 1]); // 分别取最大值
}
查询
- 计算
2 ^ k <= r - l + 1
的k
- 左端点和右端点分别查询,保证一定覆盖区间
- 结果为
max (f [L][k], f [R - 2 ^ k][k])
int RMQ (int l,int r)
{
int k = log2 (r - l + 1);
return max (f [l][k],f [r - (1 << k) + 1][k]);
}
例题
P3865 【模板】ST 表 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)