📅  最后修改于: 2023-12-03 15:35:50.728000             🧑  作者: Mango
题目名称:'| |问题 9'
题目链接:https://www.luogu.com.cn/problem/P4230
题目描述:给定一个长度为 $n$ 的序列 $A$,和一个长度为 $m$ 的操作序列 $B$,操作序列的每个元素为 $1$ 或 $2$,当操作序列中的元素为 $1$ 时,表示将序列 $A$ 中第 $i$ 到第 $j$ 的数全部加 $w$。当操作序列中的元素为 $2$ 时,表示询问序列 $A$ 中第 $i$ 到第 $j$ 的数中有多少个数在区间 $[l,r]$ 中。
本题可以通过线段树来实现。
首先,需要将每次加 $w$ 操作拆分成两个单独的操作,即将序列 $A$ 中第 $i$ 到第 $j$ 个数分别加 $w$。
然后,对于询问操作,可以使用线段树维护区间和。对于每个节点,记录该节点表示的区间的区间和。当查询区间 $[l,r]$ 时,查询区间与当前节点代表区间的交集 $[max(l,l_i),min(r,r_i)]$,其中 $l_i$ 和 $r_i$ 分别是当前节点代表的区间的左右端点。如果当前节点表示的区间与查询区间没有交集,则返回 $0$;否则,如果当前节点表示的区间完全包含查询区间,则返回该节点的区间和;否则,将查询区间拆分成两部分分别递归查询。
class SegmentTree {
public:
struct Node {
int l, r;
long long sum, delta; //记录当前节点代表区间的区间和以及懒惰标记
} tree[N << 2];
void pushup(int x) {
tree[x].sum = tree[x << 1].sum + tree[x << 1 | 1].sum;
}
void pushdown(int x) {
if (tree[x].delta) {
tree[x << 1].delta += tree[x].delta;
tree[x << 1 | 1].delta += tree[x].delta;
tree[x << 1].sum += (long long)(tree[x << 1].r - tree[x << 1].l + 1) * tree[x].delta;
tree[x << 1 | 1].sum += (long long)(tree[x << 1 | 1].r - tree[x << 1 | 1].l + 1) * tree[x].delta;
tree[x].delta = 0;
}
}
void build(int x, int l, int r) {
tree[x].l = l, tree[x].r = r, tree[x].delta = 0;
if (l == r) {
cin >> tree[x].sum;
}
else {
int mid = (l + r) >> 1;
build(x << 1, l, mid);
build(x << 1 | 1, mid + 1, r);
pushup(x);
}
}
void update(int x, int l, int r, int val) {
if (tree[x].l >= l && tree[x].r <= r) {
tree[x].delta += val;
tree[x].sum += (long long)(tree[x].r - tree[x].l + 1) * val;
}
else {
pushdown(x);
int mid = (tree[x].l + tree[x].r) >> 1;
if (l <= mid) {
update(x << 1, l, r, val);
}
if (r > mid) {
update(x << 1 | 1, l, r, val);
}
pushup(x);
}
}
long long query(int x, int l, int r) {
if (tree[x].l >= l && tree[x].r <= r) {
return tree[x].sum;
}
else {
pushdown(x);
int mid = (tree[x].l + tree[x].r) >> 1;
long long res = 0;
if (l <= mid) {
res += query(x << 1, l, r);
}
if (r > mid) {
res += query(x << 1 | 1, l, r);
}
return res;
}
}
};
int main() {
SegmentTree st;
int n, m;
cin >> n >> m;
st.build(1, 1, n);
for (int i = 1; i <= m; i++) {
int op;
cin >> op;
if (op == 1) {
int l, r, w;
cin >> l >> r >> w;
st.update(1, l, r, w);
}
else {
int l, r, L, R;
cin >> l >> r >> L >> R;
cout << st.query(1, l, r, L, R) << endl;
}
}
return 0;
}
本题是线段树模板题,使用线段树维护区间和即可。
需要注意的是每次加 $w$ 操作需要拆分成两个单独的操作,同时每个节点还需要维护懒惰标记。
同时,递归查询时需要将查询区间拆分成两部分,分别递归查询。
总的来说,本题难度不算太高,但需要认真分析并实现。