CodeForces 2094H La Vaca Saturno Saturnita 题解

注意到每次 \(k\) 除以一个大于 \(1\) 的数至少减半,所以一次查询中 \(k\) 至多变化 \(\log V\) 次。

把相同的 \(k\) 一起处理,重新描述 f(k, a, l, r)

  • 设当前位置为 \(x\),初始 \(x = l\)
  • 每次移动到最小的 \(x < y \leqslant r\),使得 \(a_y \mid k, a_y \ne 1\),如果不存在则规定 \(y = r + 1\)
  • 向答案贡献 \(k \cdot (y - x)\)
  • \(x \leftarrow y\),重复上述操作直到 \(x > r\)

只需要快速求出 \(y\),根号分治,设阈值 \(B\),考虑 \(x\) 的约数 \(d\)

  • \(d \leqslant B\),预处理 \(nxt_{i, j}\) 表示 \(i\) 之后第一个 \(j\) 的位置即可;
  • \(d > B\),预处理 \(pos_x\) 表示 \(x\) 的所有这样的约数在数组中出现过的位置,二分即可。

时间复杂度 \(O(\frac {nV} B + q \log V (B + \log n))\),常数极小。\(B = \frac n {\sqrt{q \log n}}\) 时取到最小值 \(O(n \sqrt{q \log V} + q \log n \log V)\)。实际上 \(B\) 稍微取大一点更优。

@modfisher 大神指出可以离线避免二分。

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

typedef long long i64;

const int lim = 1e5;
const int B = 200;

vector<int> divs[lim + 5];

int n, q;
int a[100005];
vector<int> pos[100005];
int nxt[100005][B + 5];

static inline void solve() {
    cin >> n >> q;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        if (a[i] > B)
            for (int j = a[i]; j <= lim; j += a[i])
                pos[j].push_back(i);
    }
    for (int i = 1; i <= B; ++i)
        nxt[n + 1][i] = n + 1;
    for (int i = n; i; --i) {
        for (int j = 1; j <= B; ++j)
            nxt[i][j] = nxt[i + 1][j];
        if (a[i] <= B)
            nxt[i][a[i]] = i;
    }
    while (q--) {
        int k, l, r;
        cin >> k >> l >> r;
        i64 ans = 0;
        for (int x = l;;) {
            int to = r + 1;
            for (auto d : divs[k]) {
                if (d > B)
                    break;
                to = min(to, nxt[x][d]);
            }
            auto it = lower_bound(pos[k].begin(), pos[k].end(), x);
            if (it != pos[k].end())
                to = min(to, *it);
            ans += 1ll * (to - x) * k;
            // cerr << "  " << k << " jump " << x << " -> " << to << " | gain " << (to - x) * k << endl;
            x = to;
            if (x > r)
                break;
            while (k % a[x] == 0)
                k /= a[x];
        }
        cout << ans << '\n';
    }
    for (int i = 1; i <= n; ++i)
        if (a[i] > B)
            for (int j = a[i]; j <= lim; j += a[i])
                pos[j].clear();
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    for (int i = 2; i <= lim; ++i)
        for (int j = i; j <= lim; j += i)
            divs[j].push_back(i);
    int T;
    cin >> T;
    while (T--)
        solve();
    return 0;
}
posted @ 2025-04-15 15:07  bluewindde  阅读(127)  评论(0)    收藏  举报