Hướng giải của Tổng Đoạn Trên Lịch Sử
Nhớ rằng hướng dẫn giải này chỉ nên sử dụng khi bế tắc, và tuyệt đối không nên sao chép mã nguồn kèm theo. Hãy tôn trọng tác giả bài tập và người viết hướng dẫn giải.
Nộp mã nguồn lời giải chính thức trước khi giải bài tập đó có thể khiến bạn bị ban.
Nộp mã nguồn lời giải chính thức trước khi giải bài tập đó có thể khiến bạn bị ban.
Tác giả:
Hướng dẫn giải
Bài toán yêu cầu tính tổng đoạn con trên các phiên bản lịch sử khác nhau. Sử dụng Persistent Segment Tree (Cây phân đoạn bền vững):
- Mỗi lần cập nhật tạo ra một phiên bản mảng mới bằng cách sao chép các node nằm trên đường đi từ gốc đến lá, các node không bị thay đổi sẽ được trỏ trực tiếp từ node mới sang cây con của phiên bản cũ.
- Duy trì mảng
rootsđể lưu chỉ số node gốc của từng phiên bản. - Khi truy vấn tổng trên phiên bản \(V\), ta gọi hàm
querybắt đầu từ gốcroots[V].
Độ phức tạp thời gian: \(O(\log N)\) mỗi thao tác.
Mã nguồn C++ mẫu
#include <bits/stdc++.h>
using namespace std;
struct Node {
long long sum;
int left, right;
};
class PersistentSegTree {
public:
vector<Node> tree;
vector<int> roots;
int n;
PersistentSegTree(int size) { n = size; roots.push_back(0); }
int build(int start, int end, const vector<int>& a) {
int id = tree.size();
tree.push_back({0, -1, -1});
if (start == end) { tree[id].sum = a[start]; return id; }
int mid = (start + end)/2;
int l = build(start, mid, a);
int r = build(mid+1, end, a);
tree[id].left = l; tree[id].right = r;
tree[id].sum = tree[l].sum + tree[r].sum;
return id;
}
int update(int prev_id, int start, int end, int pos, long long val) {
int id = tree.size();
tree.push_back(tree[prev_id]);
if (start == end) { tree[id].sum = val; return id; }
int mid = (start + end)/2;
if (pos <= mid) tree[id].left = update(tree[prev_id].left, start, mid, pos, val);
else tree[id].right = update(tree[prev_id].right, mid+1, end, pos, val);
tree[id].sum = tree[tree[id].left].sum + tree[tree[id].right].sum;
return id;
}
long long query(int node_id, int start, int end, int l, int r) {
if (r < start || end < l || node_id == -1) return 0;
if (l <= start && end <= r) return tree[node_id].sum;
int mid = (start + end)/2;
return query(tree[node_id].left, start, mid, l, r) + query(tree[node_id].right, mid+1, end, l, r);
}
};
int main(){
ios_base::sync_with_stdio(false); cin.tie(NULL);
int n, q; if (!(cin >> n >> q)) return 0;
vector<int> a(n);
for (int i=0; i<n; i++) cin >> a[i];
PersistentSegTree pst(n);
int initial_root = pst.build(0, n-1, a);
pst.roots[0] = initial_root;
int curr_ver = 0;
while (q--) {
int t; cin >> t;
if (t == 1) {
int pos; long long val; cin >> pos >> val; pos--;
int root = pst.update(pst.roots[curr_ver], 0, n-1, pos, val);
pst.roots.push_back(root);
curr_ver = pst.roots.size() - 1;
} else if (t == 2) {
int v, l, r; cin >> v >> l >> r; l--; r--;
cout << pst.query(pst.roots[v], 0, n-1, l, r) << "\n";
} else {
int v; cin >> v;
curr_ver = v;
}
}
}
Nhận xét