📌  相关文章
📜  查询给定范围内数组乘积的除数计数 |设置 2(MO 的算法)(1)

📅  最后修改于: 2023-12-03 14:55:37.738000             🧑  作者: Mango

查询给定范围内数组乘积的除数计数 | 设置2(MO 的算法)

算法介绍

这是一种基于莫队算法的解法,用于查询给定范围内数组乘积的除数计数。这个问题可以转化为求每个质因子的个数之和,而这个问题可以用线性筛素数 + 莫队算法解决。

程序代码

以下是使用 C++ 实现的代码:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 100010, maxd = 12, maxq = 100010;

int n, q, d; ll ans[maxq], sm[maxn];
int a[maxn], cnt[maxd][maxn], pri[maxn], miu[maxn];
vector<int> divs[maxn];

struct Query {
    int l, r, id;
    bool operator < (const Query& b) {
        return l/d==b.l/d ? r<b.r : l<b.l;
    }
} Q[maxq];

void init() {
    pri[0] = pri[1] = miu[1] = 1;
    for (int i=2; i<=n; ++i) {
        if (!pri[i]) pri[++pri[0]] = i, miu[i] = -1;
        for (int j=1; j<=pri[0] && i*pri[j]<=n; ++j) {
            pri[i*pri[j]] = 1;
            if (i%pri[j]==0) { miu[i*pri[j]] = 0; break; }
            else miu[i*pri[j]] = -miu[i];
        }
    }
    for (int i=1; i<=n; ++i) {
        for (int j=i; j<=n; j+=i)
            divs[j].push_back(i);
    }
    for (int i=1; i<=n; ++i) {
        for (int d: divs[a[i]])
            ++cnt[d][i];
    }
}

void upd(int l, int r, int val) { // add (+1)/substract (-1) 1 from cnt[d] with indices in [l, r]
    for (int d: divs[val])
        for (int i=lower_bound(cnt[d]+l, cnt[d]+r+1, 1)-cnt[d];
            i<=r-l; i=lower_bound(cnt[d]+i+1, cnt[d]+r+1, 1)-cnt[d])
            --cnt[d][l+i], ++cnt[d][l+i+1];
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> q, d = pow(n, 0.6666666);
    for (int i=1; i<=n; ++i) cin >> a[i];
    init();
    for (int i=1; i<=q; ++i) cin >> Q[i].l >> Q[i].r, Q[i].id = i;
    sort(Q+1, Q+1+q);
    int l=1, r=0;
    for (int i=1; i<=q; ++i) {
        int L=Q[i].l, R=Q[i].r;
        while (l>L) upd(--l, r, a[l]), ++sm[a[l]];
        while (r<R) upd(l, ++r, a[r]), ++sm[a[r]];
        while (l<L) --sm[a[l]], upd(l++, r, a[l]);
        while (r>R) --sm[a[r]], upd(l, r--, a[r]);
        ll res=0;
        for (int j=1; j<maxd; ++j)
            res += (miu[j]?sm[j]:0)*cnt[j][L-1];
        ans[Q[i].id] = res;
    }
    for (int i=1; i<=q; ++i)
        cout << ans[i] << "\n";
    return 0;
}
程序说明
  • n, q, d 表示输入的数列长度 $n$、询问数量 $q$、分块块长 $d$。
  • a[] 数组表示输入的数列。
  • cnt[][] 数组记录了区间中每个数在每个质因子下的质因子个数,即 $cnt_{i,j}$ 表示在区间 $[1,j]$ 内数 $i$ 有几个某个质因子。
  • pri[] 表示数 $i$ 是否为质数。
  • miu[] 表示 $i$ 的莫比乌斯函数值,由线性筛筛出。
  • divs[][] 是一个二维容器,$divs_i$ 存放了数 $i$ 的所有因子(包括 $1$ 和自己)的集合。
  • Query 是一个结构体,记录了区间左端点 l、区间右端点 r 和查询编号 id,并重载了用于莫队算法的小于运算符。
  • init() 函数中先把质因子个数和莫比乌斯函数值线性筛出来,再预处理出每个数的所有因子。
  • upd(l, r, val) 函数中,先枚举每个因子 d,对于在左右端点区间内的数,限制其按顺序转移,每次把左端点区间加上 $1$,右端点区间缩减 $1$(如果有 $-1$ 的话),可以快速维护区间内每个数的各个质因子的个数。
  • 主函数先读入 $n$ 和 $q$,然后读入长度为 $n$ 的数列 a[]。调用 init() 初始化,读入 $q$ 个查询(使用莫队算法查询每个区间内的质因子个数和),并按照左端点所在块排序。循环中,维护区间 $[l,r]$,不断调用 upd 更新区间,直到涵盖区间 $[L,R]$,之后依次重复这个过程。计算完每个区间的答案后输出即可。
性能分析

这个算法的时间复杂度为 $O(n^{\frac{2}{3}} q \sqrt{n} + n \log n)$,其中前一项为莫队算法的复杂度,后一项主要用于初始化相关的数组。由于这个算法需要预处理 $n$ 个数的各个质因子个数,加上线性递推求莫比乌斯函数,所以时间常数比较大,以至于无法通过本场提高组 4 的数据。