📌  相关文章
📜  Q 查询的索引范围 [L, R] 中具有奇数除数的元素的计数(1)

📅  最后修改于: 2023-12-03 15:19:37.431000             🧑  作者: Mango

查询具有奇数除数的元素计数

本文将介绍如何使用线段树和数学知识来解决查询具有奇数除数的元素计数问题。

问题描述

给定一个长度为 $n$ 的序列 $a_1, a_2, \dots, a_n$,以及 $q$ 个查询。对于每个查询,给出两个整数 $L$ 和 $R$,请求出区间 $[L,R]$ 中具有奇数个因数的元素个数。

解决方案
思路

我们将通过对每个数进行分解质因数,并计算其因数个数来判断一个数是否具有奇数个因数。设 $a_i$ 的质因数分解式为 $a_i = p_1^{k_1}p_2^{k_2}\cdots p_t^{k_t}$,则 $a_i$ 的因数个数为 $(k_1+1)\times(k_2+1)\times\cdots\times(k_t+1)$。由于一个数具有奇数个因数当且仅当它的每个质因数的指数都是偶数,因此我们可以对于每个 $a_i$,计算它质因数分解式中每个质因数的指数之和,若该和为偶数,则 $a_i$ 具有奇数个因数,否则不具有。

显然,我们可以通过线段树来维护区间内每个数的质因数分解式中每个质因数指数的和。对于每次查询,只需要查询区间内满足上述条件的元素个数即可。

实现

我们首先需要预处理出所有质数,以及每个数的质因数分解式,这可以使用线性筛法求解。然后,我们设计线段树的节点,其应维护区间内每个数的质因数分解式的指数之和。

struct Node {
    int sum[MAX_PRIME_COUNT];
};

然后,我们可以采用分治的方法进行线段树的建立和维护。具体地,对于每个节点,我们将其区间一分为二,并递归地建立子节点。当需要合并两个节点时,我们只需要将它们区间内每个质因子的指数之和相加即可。

void build(int l, int r, int rt = 1) {
    if (l == r) {
        for (int i = 0; i < primeCount; ++i) {
            int p = primeList[i];
            while (a[l] % p == 0) {
                ++tree[rt].sum[i];
                a[l] /= p;
            }
        }
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, rt << 1);
    build(mid + 1, r, rt << 1 | 1);
    for (int i = 0; i < primeCount; ++i) {
        tree[rt].sum[i] = tree[rt << 1].sum[i] + tree[rt << 1 | 1].sum[i];
    }
}

对于每个查询,我们可以通过查询区间内每个数的质因数分解式的因数个数是否为偶数来统计具有奇数个因数的元素数量。

int query(int l, int r, int L, int R, int rt = 1) {
    if (l >= L && r <= R) {
        int cnt = 0;
        for (int i = 0; i < primeCount; ++i) {
            if (tree[rt].sum[i] & 1) {
                cnt = 1;
                break;
            }
        }
        return cnt;
    }
    int mid = (l + r) >> 1, cnt = 0;
    if (L <= mid) cnt += query(l, mid, L, R, rt << 1);
    if (R > mid) cnt += query(mid + 1, r, L, R, rt << 1 | 1);
    return cnt;
}
参考代码

完整的参考代码如下: