返回
DQUERY - D-query:动态求区间不重复数目
后端
2023-10-04 15:28:56
算法简介
莫队算法
莫队算法是一种离线算法,它将查询按时间戳排序,然后按照时间戳顺序依次处理。对于每个查询,莫队算法都会维护一个滑动窗口,该窗口包含了当前查询涉及的元素。算法通过巧妙地更新窗口来高效地计算查询结果,避免了重复计算。
主席树
主席树是一种数据结构,它可以高效地存储和查询一段区间内的信息。主席树由多个节点组成,每个节点代表一个区间,并记录了该区间内某个特定元素出现的次数。通过主席树,我们可以快速查询区间内某个元素出现的次数。
算法实现
莫队算法实现
struct Query {
int l, r, id; // 左边界、右边界、查询 id
};
bool cmp(const Query& a, const Query& b) {
return a.l / BLOCK_SIZE < b.l / BLOCK_SIZE ||
(a.l / BLOCK_SIZE == b.l / BLOCK_SIZE && a.r < b.r);
}
int main() {
// 输入数据
int n, q;
cin >> n >> q;
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
// 预处理莫队块
const int BLOCK_SIZE = sqrt(n);
int num_blocks = (n + BLOCK_SIZE - 1) / BLOCK_SIZE;
vector<Query> queries(q);
for (int i = 0; i < q; i++) {
int l, r;
cin >> l >> r;
queries[i] = {l, r, i};
}
// 排序查询
sort(queries.begin(), queries.end(), cmp);
// 莫队算法处理查询
unordered_map<int, int> freq;
int ans[q];
int cur_l = 1, cur_r = 0, cur_ans = 0;
for (const Query& query : queries) {
while (cur_l > query.l) {
freq[a[--cur_l]]++;
if (freq[a[cur_l]] == 1) cur_ans++;
}
while (cur_r < query.r) {
freq[a[++cur_r]]++;
if (freq[a[cur_r]] == 1) cur_ans++;
}
while (cur_l < query.l) {
freq[a[cur_l++]]--;
if (freq[a[cur_l - 1]] == 0) cur_ans--;
}
while (cur_r > query.r) {
freq[a[cur_r--]]--;
if (freq[a[cur_r + 1]] == 0) cur_ans--;
}
ans[query.id] = cur_ans;
}
// 输出查询结果
for (int i = 0; i < q; i++) cout << ans[i] << endl;
return 0;
}
主席树实现
struct Node {
int sum; // 区间内某个元素出现的次数
Node* left, *right; // 左右子树
};
// 构建主席树
Node* build(int l, int r, vector<int>& a) {
if (l == r) {
Node* node = new Node();
node->sum = (a[l] == 0);
return node;
}
int mid = (l + r) / 2;
Node* node = new Node();
node->left = build(l, mid, a);
node->right = build(mid + 1, r, a);
node->sum = node->left->sum + node->right->sum;
return node;
}
// 查询区间内某个元素出现的次数
int query(Node* root, int l, int r, int ql, int qr, int x) {
if (l > r || l > qr || r < ql) return 0;
if (l >= ql && r <= qr) {
if (x == 0) return root->sum;
else return root->right->sum;
}
int mid = (l + r) / 2;
return query(root->left, l, mid, ql, qr, x) +
query(root->right, mid + 1, r, ql, qr, x);
}
int main() {
// 输入数据
int n, q;
cin >> n >> q;
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
// 构建主席树
Node* root = build(0, n - 1, a);
// 处理查询
for (int i = 0; i < q; i++) {
int l, r, x;
cin >> l >> r >> x;
int ans = query(root, 0, n - 1, l - 1, r - 1, x);
cout << ans << endl;
}
return 0;
}
优缺点对比
算法 | 优点 | 缺点 |
---|---|---|
莫队算法 | 离线算法,可以处理海量查询 | 时间复杂度与查询数量相关,适用于离线场景 |
主席树 | 在线算法,可以高效处理单点修改 | 空间复杂度较高,适用于在线场景 |
总结
D-query 问题是区间查询问题的一个典型案例,莫队算法和主席树是解决此类问题的两大有力算法。莫队算法适用于离线场景,时间复杂度与查询数量相关;主席树适用于在线场景,空间复杂度较高。选择合适的算法需要根据实际场景和需求进行权衡。