标准在线主席树 HDU - 4348【To the moon】
标准在线主席树 HDU - 4348【To the moon】
https://cn.vjudge.net/contest/304073#problem/G
题意
给定 n 个数和 m 次操作,起始时间为0,操作分为四种:
-
Q x y —— 询问 [x, y] 区间当前时间节点的和
-
C x y z —— 把 [x, y] 区间里的所有数加上 z,当前时间节点 +1
-
H x y z —— 询问 [x, y] 区间在 z 时间节点的和
-
B x —— 把当前时间节点变成 x
分析
之前做过的几题都属于“权值线段树”(树里每个节点存的值 \(sum\) 表示的是当前数的个数),因此需要进行离散化,用标号表示每个数。
而这一题需要建的树中的每个节点存的值 \(sum\) 就是和常规线段树一样,是真正意义的区间和(因为题目需要求区间和)。
对于区间更新操作来说,我们可以用 lazy 数组。 在从上往下递归的时候,可以把这个父节点的 sum,直接更新。当遇到查询区间和给定区间正好完全重合的时候,我们需要更新 lazy。 这样做的话可以省去 pushdown 的复杂操作。
在查询的时候,我们需要把所有要找的区间 lazy 都加上。
具体操作见代码。
代码
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 1e5 + 5;
int n, m, cnt;
ll a[maxn];
int root[maxn];
struct node {
int l, r;
ll sum, lazy;
}T[maxn*40];
void init() {
cnt = 0;
}
void build(int l, int r, int &rt) {
rt = ++ cnt;
T[rt].l = l;
T[rt].r = r;
T[rt].sum = a[l];
T[rt].lazy = 0;
if(l == r) {
return ;
}
int mid = (l+r) / 2;
build(l, mid, T[rt].l);
build(mid+1, r, T[rt].r);
T[rt].sum = T[T[rt].l].sum + T[T[rt].r].sum;
}
void update(int L, int R, int l, int r, int &x, int y, ll c) {
T[++cnt] = T[y];
T[cnt].sum += c*(R-L+1);// 把每个区间的增量都算上
x = cnt;
if(L == l && R == r) {
T[cnt].lazy += c; // 正好重合时更新lazy标记
return ;
}
int mid = (l+r) / 2;
// 区间更新
if(mid >= R) {
update(L, R, l, mid, T[x].l, T[y].l, c);
}
else if(mid < L) {
update(L, R, mid+1, r, T[x].r, T[y].r, c);
}
else {
update(L, mid, l, mid, T[x].l, T[y].l, c);
update(mid+1, R, mid+1, r, T[x].r, T[y].r, c);
}
}
ll query(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) {
return T[rt].sum;
}
ll ans = T[rt].lazy*(R-L+1); // 每个需要找的区间都得加
int mid = (l+r) / 2;
if(mid >= R) {
ans += query(L, R, l, mid, T[rt].l);
}
else if(mid < L) {
ans += query(L, R, mid+1, r, T[rt].r);
}
else {
ans += query(L, mid, l, mid, T[rt].l);
ans += query(mid+1, R, mid+1, r, T[rt].r);
}
return ans;
}
int main() {
while(~scanf("%d%d", &n, &m)) {
init();
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]); // 非权值线段树,不需要离散化,存的是原数
}
build(1, n, root[0]);
// cout << T[1].sum << endl;
int now_time = 0;
for(int i = 1; i <= m; i++) {
char f[10];
int x, y, t;
ll c;
scanf("%s", f);
if(f[0] == 'Q') {
scanf("%d%d", &x, &y);
printf("%lld\n", query(x, y, 1, n, root[now_time]));
}
else if(f[0] == 'C') {
scanf("%d%d%lld", &x, &y, &c);
now_time ++;
update(x, y, 1, n, root[now_time], root[now_time-1], c);
}
else if(f[0] == 'H') {
scanf("%d%d%d", &x, &y, &t);
printf("%lld\n", query(x, y, 1, n, root[t]));
}
else {
scanf("%d", &t);
now_time = t;
}
}
}
return 0;
}