Files
luogu/solutions/P6650.md
T

5.1 KiB
Raw Blame History

题意简述

给定长度为 n 的序列 a 和整数 $k$。选择一个区间 $[l,r]$,满足区间最大值与最小值之差不超过 $k$。

将区间乘积质因数分解:


\prod_{i=l}^r a_i = \prod_p p^{E_p}。

对于每个质数 $p$,把 E_p 拆成若干个互不相同的正整数 $e_{p,1},e_{p,2},\dots,e_{p,t_p}$,并选取对应的质数幂 $p^{e_{p,i}}$。所有选取的数必须互不相同。

得分为所有选取数的约数个数之和:


\sum_p \sum_i (e_{p,i}+1)。

求最大得分。

思路分析

单个质数的贡献

固定一个质数 $p$,设它在区间乘积中的总指数为 $E$。

若把 E 拆成 t 个互不相同的正整数,则这些数的和至少为


1+2+\cdots+t = \frac{t(t+1)}2。

E \ge \frac{t(t+1)}2 时,可以取 1,2,\dots,t-1 以及 $E-\frac{t(t-1)}2$,它们互不相同且总和为 $E$。

因此最大可行的 t 为满足 \frac{t(t+1)}2 \le E 的最大整数,即


t_{\max} = \left\lfloor \frac{\sqrt{8E+1}-1}{2} \right\rfloor。

对应的得分为


\sum_i (e_i+1) = E + t_{\max}。

定义


gx(E) = E + \left\lfloor \frac{\sqrt{8E+1}-1}{2} \right\rfloor

则一个质数指数为 E 时的贡献为 $gx(E)$。特别地,$gx(0)=0$。

区间选择

对于固定的右端点 $r$,如果区间 [l,r] 合法,那么区间 [l+1,r] 也合法,因为子区间的极差不会增大。

又因为窗口扩大时,每个质数的指数 E_p 只会增加或不增加,同时 gx(E) 单调递增,所以固定 r 时,区间越长得分越高。于是只需要找到最小的合法左端点。

使用双指针维护滑动窗口。令左端点 l 单调右移,并用两个单调队列分别维护当前窗口的最大值和最小值。当窗口极差超过 k 时,不断右移 $l$,同时删除 a_l 的质因子贡献。

算法流程

  1. 预处理最小质因子 $spf$,用于快速分解每个 $a_i$。
  2. 将每个 a_i 分解为质因子列表,保留重数。
  3. 维护滑动窗口 $[l,r]$,其中:
    • cnt[p] 表示当前窗口内质数 p 的总指数;
    • cur 表示当前窗口得分 $\sum_p gx(cnt[p])$
    • 两个单调队列维护窗口最大值和最小值。
  4. 枚举右端点 $r$
    • a_r 的质因子加入窗口,更新 cnt 和 $cur$
    • r 加入单调队列;
    • 若当前窗口极差超过 $k$,则不断右移 $l$,并删除 a_l 的质因子贡献;
    • 用当前 cur 更新答案。
  5. 输出答案。

复杂度分析

设 $A=\max a_i \le 10^5$。

  • 预处理最小质因子:$O(A \log \log A)$。
  • 分解所有 $a_i$:总质因子个数为 $O(n \log A)$。
  • 滑动窗口:每个元素的每个质因子加入一次、删除一次,复杂度 $O(n \log A)$。
  • 单调队列:$O(n)$。

总时间复杂度为


O(A \log \log A + n \log A)。

参考代码

#include <bits/stdc++.h>
using namespace std;

int n, k;
int a[100005];
int spf[100005];          // 最小质因子
vector<int> fac[100005];  // 每个数的质因子列表(带重数)
int cnt[100005];          // 当前窗口内每个质数的总指数
long long cur, ans;
deque<int> dq_max, dq_min;

// 计算 e 的贡献
long long gx(int e) {
    if (e == 0) return 0;
    long long t = (sqrt(8.0 * e + 1) - 1) / 2;
    while ((t + 1) * (t + 2) / 2 <= e) ++t;
    while (t * (t + 1) / 2 > e) --t;
    return e + t;
}

// 预处理最小质因子
void init_spf() {
    for (int i = 2; i < 100005; ++i) {
        if (!spf[i]) {
            spf[i] = i;
            if ((long long)i * i < 100005) {
                for (int j = i * i; j < 100005; j += i) {
                    if (!spf[j]) spf[j] = i;
                }
            }
        }
    }
}

// 分解质因数
void get_fac(int idx, int x) {
    while (x > 1) {
        int p = spf[x];
        while (x % p == 0) {
            fac[idx].push_back(p);
            x /= p;
        }
    }
}

int main() {
    scanf("%d%d", &n, &k);
    init_spf();

    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        get_fac(i, a[i]);
    }

    int l = 1;
    cur = 0, ans = 0;

    for (int r = 1; r <= n; ++r) {
        // 加入 a[r]
        for (int p : fac[r]) {
            int e = cnt[p];
            cur += gx(e + 1) - gx(e);
            cnt[p]++;
        }

        while (!dq_max.empty() && a[dq_max.back()] <= a[r]) dq_max.pop_back();
        dq_max.push_back(r);

        while (!dq_min.empty() && a[dq_min.back()] >= a[r]) dq_min.pop_back();
        dq_min.push_back(r);

        // 收缩左边界
        while (1) {
            while (!dq_max.empty() && dq_max.front() < l) dq_max.pop_front();
            while (!dq_min.empty() && dq_min.front() < l) dq_min.pop_front();

            int mx = a[dq_max.front()];
            int mn = a[dq_min.front()];

            if (mx - mn <= k) break;

            // 移除 a[l]
            for (int p : fac[l]) {
                int e = cnt[p];
                cur += gx(e - 1) - gx(e);
                cnt[p]--;
            }
            l++;
        }

        ans = max(ans, cur);
    }

    printf("%lld\n", ans);
    return 0;
}