返回

DQUERY - D-query:动态求区间不重复数目

后端

算法简介

莫队算法

莫队算法是一种离线算法,它将查询按时间戳排序,然后按照时间戳顺序依次处理。对于每个查询,莫队算法都会维护一个滑动窗口,该窗口包含了当前查询涉及的元素。算法通过巧妙地更新窗口来高效地计算查询结果,避免了重复计算。

主席树

主席树是一种数据结构,它可以高效地存储和查询一段区间内的信息。主席树由多个节点组成,每个节点代表一个区间,并记录了该区间内某个特定元素出现的次数。通过主席树,我们可以快速查询区间内某个元素出现的次数。

算法实现

莫队算法实现

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 问题是区间查询问题的一个典型案例,莫队算法和主席树是解决此类问题的两大有力算法。莫队算法适用于离线场景,时间复杂度与查询数量相关;主席树适用于在线场景,空间复杂度较高。选择合适的算法需要根据实际场景和需求进行权衡。