每日一题做题记录,参考官方和三叶的题解 |
需实现单点修改和区间求和两个功能,所以想到使用树状数组。
方法 | 功能 |
---|---|
lowbit(key) | 低位运算,计算非负整数 k e y key key在二进制表示下最低位的 1 1 1与其后所有的 0 0 0所构成的数值 |
add(idx, val) | 单点增加,将 i d x idx idx处的值增加 v a l val val(并更新其他节点) |
query(key) | 查询前缀和,查询序列前 k e y key key项和,即 log n \log n logn个区间的总和 |
class NumArray {
//树状数组定义
int[] tr;
int lowbit(int x) {
return x & -x;
}
void add(int x, int u) {
for(int i = x; i <= n; i += lowbit(i))
tr[i] += u;
}
int query(int x) {
int res = 0;
for(int i = x; i > 0; i -= lowbit(i))
res += tr[i];
return res;
}
int[] num;
int n;
public NumArray(int[] nums) {
num = nums;
n = num.length;
tr = new int[n + 10];
for(int i = 0; i < n; i++)
add(i + 1, num[i]);
}
public void update(int index, int val) {
add(index + 1, val - num[index]); //更新各父节点
num[index] = val; //修改目标
}
public int sumRange(int left, int right) {
return query(right + 1) - query(left); //前缀和相减
}
}
【基本和Java一样,除了一小点vector
的处理】
class NumArray {
public:
//树状数组定义
vector<int> tr;
int lowbit(int x) {
return x & -x;
}
void add(int x, int u) {
for(int i = x; i <= n; i += lowbit(i))
tr[i] += u;
}
int query(int x) {
int res = 0;
for(int i = x; i > 0; i -= lowbit(i))
res += tr[i];
return res;
}
vector<int> num;
int n;
NumArray(vector<int>& nums) {
num = nums;
n = num.size();
tr.resize(n + 10);
for(int i = 0; i < n; i++)
add(i + 1, num[i]);
}
void update(int index, int val) {
add(index + 1, val - num[index]); //更新各父节点
num[index] = val; //修改目标
}
int sumRange(int left, int right) {
return query(right + 1) - query(left); //前缀和相减
}
};
一个比树状数组更更通用的方法。
pushdown()
方法里,即从父辈节点开始向下更新值。方法 | 功能 |
---|---|
pushup(sta) | 更新 s t a sta sta的所有父辈节点(由子向上) |
build(sta, l, r) | 从编号 s t a sta sta的节点开始构造范围为 [ l , r ] [l,r] [l,r]的树节点 |
update(sta, idx, val) | 从编号 s t a sta sta的节点开始,在 i d x idx idx处的值增加 v a l val val |
query(sta, l, r) | 从编号 s t a sta sta的节点开始,查询 [ l , r ] [l,r] [l,r]的区间和 |
class NumArray {
Node[] tr;
class Node {
int l, r, v;
Node(int _l, int _r) {
l = _l;
r = _r;
}
}
void build(int sta, int l, int r) {
tr[sta] = new Node(l, r);
if(l == r)
return;
//递归构造
int m = l + r >> 1;
build(sta << 1, l, m);
build(sta << 1 | 1, m + 1, r);
}
void pushup(int sta) {
tr[sta].v = tr[sta << 1].v + tr[sta << 1 | 1].v;
}
void update(int sta, int idx, int val) {
if(tr[sta].l == idx && tr[sta].r == idx) {
tr[sta].v += val;
return;
}
int m = tr[sta].l + tr[sta].r >> 1;
if(idx <= m)
update(sta << 1, idx, val);
else
update(sta << 1 | 1, idx, val);
pushup(sta);
}
int query(int sta, int l, int r) {
if(l <= tr[sta].l && tr[sta].r <= r)
return tr[sta].v;
int m = tr[sta].l + tr[sta].r >> 1;
int res = 0;
if(l <= m)
res += query(sta << 1, l, r);
if(r > m)
res += query(sta << 1 | 1, l, r);
return res;
}
int[] num;
public NumArray(int[] nums) {
num = nums;
int n = num.length;
tr = new Node[n * 4];
build(1, 1, n);
for(int i = 0; i < n; i++)
update(1, i + 1, num[i]);
}
public void update(int index, int val) {
update(1, index + 1, val - num[index]);//更新各父节点
num[index] = val; //修改目标
}
public int sumRange(int left, int right) {
return query(1, left + 1, right + 1);
}
}
这个地方是有点复杂的,没有定义新的类型,靠多维护几个参数解决。
class NumArray {
private:
vector<int> tr;
int n;
void build(int sta, int l, int r, vector<int> &nums) {
if(l == r) {
return;
}
int m = (l + r) >> 1;
build(sta << 1, l, m, nums);
build(sta << 1 | 1, m + 1, r, nums);
}
void pushup(int sta) {
tr[sta] = tr[sta << 1] + tr[sta << 1 | 1];
}
void update(int sta, int l, int r, int idx, int val) {
if(l == idx && r == idx){
tr[sta] += val;
return;
}
int m = (l + r) >> 1;
if(idx <= m)
update(sta << 1, l, m, idx, val);
else
update(sta << 1 | 1, m + 1, r, idx, val);
pushup(sta);
}
int query(int sta, int l, int r, int left, int right) {
if(left <= l && right >= r)
return tr[sta];
int m = (l + r) >> 1;
int res = 0;
if(left <= m)
res += query(sta << 1, l, m, left, right);
if(right > m)
res += query(sta << 1 | 1, m + 1, r, left, right);
return res;
}
public:
vector<int> num;
NumArray(vector<int> &nums) :tr(nums.size() * 4), n(nums.size()) {
num = nums;
build(1, 1, n, num);
for(int i = 0; i < n; i++)
update(1, 1, n, i + 1, num[i]);
}
void update(int index, int val) {
update(1, 1, n, index + 1, val - num[index]); //更新各父节点
num[index] = val; //修改目标
}
int sumRange(int left, int right) {
return query(1, 1, n, left + 1, right + 1);
}
};
看起来很简单的题目,结果完全触及知识盲区,get了两个处理区间的数据结构,然后三叶姐姐总结对于区间类问题考虑问题的方向:
1.简单求区间和,用「前缀和」
2.多次将某个区间变成同一个数,用「线段树」
3.其他情况,用「树状数组」
另:
题目要求 | 前缀和 | 树状数组 | 差分 | 线段树 | 备注 |
---|---|---|---|---|---|
数组不变,求区间和 | √ | √ | √ | ||
多次修改某个数(单点),求区间和 | √ | √ | 本题符合 | ||
多次修改某个区间,输出最终结果 | √ | ||||
多次修改某个区间, 求区间和 | √ | √ | 根据修改区间范围大小选择 | ||
多次将某个区间变成同一个数, 求区间和 | √ | √ | 根据修改区间范围大小选择 |
欢迎指正与讨论! |