前言
一、树状数组
1.功能介绍
树状数组可以认为是线段树的青春版
树状数组它有着丰富的功能,虽然不及线段树,但它代码简洁,思维清晰,速度也更快
它和st表有点相似,但功能相对更丰富些,树状数组仅靠一维数组便维护了整个区间具体为:
c
[
k
]
=
∑
i
=
k
−
l
o
w
b
i
t
(
k
)
+
1
k
a
[
i
]
c[k]=\sum_{i=k-lowbit(k)+1}^{k}a[i]
c[k]=i=k−lowbit(k)+1∑ka[i]
l
o
w
b
i
t
(
x
)
=
x
&
(
−
x
)
lowbit(x)=x\&(-x)
lowbit(x)=x&(−x)
它支持单点修改、区间修改(需要进行前缀变换)、单点查询、区间查询功能
树状数组的区间查询建立在前缀和的基础上,因此想让它求区间最值还得需要一定的变换,比较复杂,不推荐
int query(int x, int y) {
int ans = 0; // (log n) ^ 2
while (y >= x) {
ans = max(a[y], ans);
y--;
for (; y - lowbit(y) >= x; y -= lowbit(y)) ans = max(c[y], ans);
}
return ans;
}
所以它的应用场景一般就集中在数据查询,和逆序对上
此外,值得一提的是它可以与莫队结合求区间逆序对
贴一张图方便理解,图源 https://blog.csdn.net/bestsort/article/details/80796531
2.例题 p3374
单点修改、区间求和
题目链接 洛谷p3374
代码如下:
#include <iostream>
using namespace std;
const int maxn = 5e5 + 5;
int n, m;
int a[maxn], c[maxn];
int lowbit(int x) { return x & (-x); } // x 最后一位是 1 的位数
void update(int x, int val) { // c[k] = a[k-lowbit(i)+1] + ... + a[k]
for (int i = x; i <= n; i += lowbit(i)) c[i] += val;
}
int query(int x) { // Σ a[i], i <= x
int sum = 0;
for (int i = x; i > 0; i -= lowbit(i)) sum += c[i];
return sum;
}
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), update(i, a[i]);
for (int i = 1; i <= m; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
if (x == 1) update(y, z);
if (x == 2) printf("%d\n", query(z) - query(y - 1));
}
return 0;
}
2.例题 逆序对p1908
下面给出求逆序对的模板,注意
q
u
e
r
y
(
x
)
=
∑
i
=
1
x
a
[
i
]
query(x)=\sum_{i=1}^{x}a[i]
query(x)=i=1∑xa[i]在查询时注意区间边界
题目链接 洛谷p1908
#include <algorithm>
#include <iostream>
#define int long long
using namespace std;
const int maxn = 1e6 + 6;
int n;
int a[maxn], b[maxn], c[maxn];
int lowbit(int x) { return x & (-x); }
void dispersed() {
for (int i = 1; i <= n; i++) b[i] = a[i];
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + n + 1, a[i]) - b;
}
void update(int x, int val) {
for (int i = x; i <= n; i += lowbit(i)) c[i] += val;
}
int query(int x) { // Σ a[i], i <= x
int res = 0;
for (int i = x; i > 0; i -= lowbit(i)) res += c[i];
return res;
}
signed main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
scanf("%lld", &n);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
dispersed();
int ans = 0;
for (int i = n; i >= 1; i--) {
update(a[i], 1);
ans += query(a[i] - 1);
}
printf("%lld\n", ans);
return 0;
}
二、st表
1.功能介绍
st表在RMQ问题上有着很优秀的复杂度(区间最大、最小查询),代码非常简洁
当然它也有较大的局限性,一般仅用于RMQ这种 “可重复贡献” 的问题,
例如区间GCD、区间按位与也在st表功能范畴内,不过需要注意的是GCD问题上 st 表复杂度不如线段树,
2.例题 p3865
题目链接:洛谷 p3865
#include <iostream>
using namespace std;
const int logn = 21;
const int maxn = 2e6 + 6;
int n, m;
int st[maxn][logn + 1], lg[maxn + 1];
void getST() {
lg[1] = 0, lg[2] = 1;
for (int i = 3; i < maxn; i++) lg[i] = lg[i / 2] + 1;
for (int j = 1; j <= logn; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++) // max{i, i+2^j-1}
st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &st[i][0]);
getST();
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
int s = lg[y - x + 1];
printf("%d\n", max(st[x][s], st[y - (1 << s) + 1][s]));
}
return 0;
}
三、bitset
1.功能介绍
bitset 有许多有用的功能,下面通过代码以及注释的形式给出其主要功能
注意:bs.all()函数是c++11时出现的
2. 调试过程代码
代码如下 :
#include <bitset>
#include <iostream>
using namespace std;
int main() {
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
cout << "num.1-------------------------" << endl;
bitset<8> bs;
cout << bs << endl; // 默认初始化全为0
cout << bs.to_ulong() << endl; // 0
cout << bs.to_string() << endl; // 00000000
cout << bs[0] << " " << bs[1] << endl; // 从低位开始编号
cout << bs.count() << endl; // 统计1的个数
cout << bs.size() << endl; // 统计位数
cout << endl;
cout << "num.2--------------------------" << endl;
bitset<8> bs1(7); // 单目运算
cout << bs1 << endl; // 00000111
cout << (bs1 << 1) << endl; // 左移一位,多者溢出,少者补0
cout << (bs1 >> 1) << endl; // 右移一位,多者溢出,少者补0
cout << ~bs1 << endl; // 取反
cout << endl;
cout << "num.3--------------------------" << endl;
bitset<8> bs2("1010"); // 双目运算
cout << bs2 << endl; // 00001010
cout << (bs2 | bs1) << endl; // 记得带括号,按位或
cout << (bs2 & bs1) << endl; // 按位与
cout << (bs2 ^ bs1) << endl; // 按位异或
cout << (bs2 == bs1) << endl; // 按位比较
cout << (bs2 != bs1) << endl; // 按位比较
cout << endl;
cout << "num.4--------------------------" << endl;
bitset<8> bs3("11100000110"); // 前面的溢出,只赋值后面的位
cout << bs3 << endl; // 00000110
cout << bs3.any() << endl; // bool类型 全0返回0,有1返回1
cout << bs3.none() << endl; // bool类型 全0返回1,有1返回0
cout << bs3.all() << endl; // bool类型 全1返回1,有0返回0
cout << bs3.flip(2) << endl; // 第2位取反
cout << bs3.flip() << endl; // 全取反
cout << endl;
cout << "num.5--------------------------" << endl;
char s[10] = "1011";
bitset<8> bs4(s);
cout << bs4 << endl; // 需要 -std=c++11 ,比赛就不能用了
cout << bs4.set(2) << endl; // 第2位置1
cout << bs4.set(3, 1) << endl; // 第3位置1
cout << bs4.set() << endl; // 全部置1
cout << bs4.reset(2) << endl; // 第2位清零
cout << bs4.reset() << endl; // 全部清零
return 0;
}