#P3666. 第3题-正整数重量和
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 42
            Accepted: 6
            Difficulty: 7
            
          
          
          
                       所属公司 : 
                              科大讯飞
                                
            
                        
              时间 :2025年9月13日
                              
                      
          
 
- 
                        算法标签>滑动窗口          
 
第3题-正整数重量和
思路
这个问题可以分解为两个主要步骤:
- 高效计算每个数字的重量。
 - 在计算出的重量序列上,寻找长度不超过 k 的最大连续子段和。
 
第一步:计算每个数字的重量
一个数字 x 的重量是其质因数分解中最大的指数。要计算它,我们首先需要对 x 进行质因数分解。
- 
朴素方法:对每个数字 ai 进行试除法,从 2 开始到 ai 来寻找质因子。对于单个数字 ai≈107,这个方法是可行的。但由于测试数据组数 T 和序列长度 n 的总和可能很大(最多 2×105),对每个数字都这样做会导致超时。
 - 
优化方法:线性筛预处理 为了加速质因数分解,我们可以预先计算出 2 到 107 范围内每个数的最小质因子 (Smallest Prime Factor, SPF)。这可以通过类似于埃氏筛或线性筛的方法在 O(VloglogV) 或 O(V) 的时间内完成,其中 V 是 ai 的最大值(107)。
预处理步骤 (Sieve):
- 创建一个数组 
spf,大小为MAX_A(107+1)。 - 初始化 
spf[i] = i。 - 遍历从 2 到 MAX_A 的数 
i。如果spf[i] == i,说明i是一个质数。 - 对于这个质数 
i,遍历它的倍数j = i*i, i*i+i, ...。如果j的最小质因子尚未被标记(即spf[j] == j),则将其标记为i(spf[j] = i)。 
计算重量步骤: 有了
spf数组,分解任何数 x 就变得非常快。- 当 x>1 时,不断用 p=spf[x] 去除 x,并计数。
 - 例如,对于 x=90,spf[90]=2。我们用 2 去除 90,得到 45,指数为 1。现在 x=45。
 - spf[45]=3。我们用 3 去除 45,得到 15,再除得到 5,指数为 2。现在 x=5。
 - spf[5]=5。我们用 5 去除 5,得到 1,指数为 1。现在 x=1,分解结束。
 - 记录过程中出现的最大指数即为 x 的重量。 这个分解过程的时间复杂度约为 O(logx)。由于所有测试用例的 n 的总和有限,这个方法是足够高效的。
 
 - 创建一个数组 
 
第二步:求解长度不超过 k 的最大连续子段和
在第一步之后,我们得到了一个由每个 ai 的重量组成的新的序列 w1,w2,…,wn。问题转化为:在这个 w 序列上,找到一个长度不超过 k 的连续子段,使其和最大。
这是一个经典的滑动窗口问题。我们可以使用前缀和与单调队列来解决。
- 
前缀和: 首先,计算重量序列 w 的前缀和数组 P。定义 P[i]=∑j=1iwj,并且 P[0]=0。 那么,子段 wj,…,wi 的和就可以表示为 P[i]−P[j−1]。
 - 
问题转化: 我们的目标是找到 max1≤i≤n,max(1,i−k+1)≤j≤i ∑l=jiwl。 使用前缀和,这等价于找到 max1≤i≤n(P[i]−P[j−1]),其中 j−1 的范围是 [max(0,i−k),i−1]。 对于每一个固定的右端点 i,我们想让 P[i] 减去一个尽可能小的 P[j−1]。所以问题变成了:

 - 
单调队列优化: 当我们从 i 移动到 i+1 时,寻找最小值的窗口 [max(0,i−k),i−1] 也随之滑动。我们可以使用一个双端队列(deque)来维护这个滑动窗口内的最小前缀和值的索引。
- 队列中存储的是前缀和数组 P 的索引。
 - 队列中的索引 j 对应的 P[j] 值是单调递增的。
 - 遍历 i 从 1 到 n:
a. 移除队首:检查队首的索引 
dq.front()是否已经滑出窗口(即dq.front() < i - k)。如果是,则从队首弹出。 b. 计算当前最大和:此时,队首元素dq.front()就是当前窗口 [i−k,i−1] 中 P 值最小的索引。我们用 P[i]−P[dq.front()] 来更新全局的最大和。 c. 维护单调性:从队尾开始,如果 P[dq.back()]≥P[i],就将其弹出。这保证了队列中 P 值的单调递增性。 d. 入队:将当前索引 i 加入队尾。 
 
这个过程对每个元素处理一次,所以时间复杂度是 O(n)。
C++
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <deque>
// a_i 的最大值
const int MAX_A = 10000001;
// spf[i] 存储 i 的最小质因子 (Smallest Prime Factor)
int spf[MAX_A];
// 使用筛法预处理所有小于 MAX_A 的数的最小质因子
void sieve() {
    // 初始化 spf[i] = i
    std::iota(spf, spf + MAX_A, 0);
    for (int i = 2; i * i < MAX_A; ++i) {
        // 如果 i 是一个质数 (其最小质因子是它自己)
        if (spf[i] == i) {
            // 遍历 i 的倍数 j,并更新它们的最小质因子
            for (int j = i * i; j < MAX_A; j += i) {
                if (spf[j] == j) { // 如果 j 的最小质因子还未被标记
                    spf[j] = i;
                }
            }
        }
    }
}
// 计算数字 x 的重量
int get_weight(int x) {
    if (x == 1) return 0;
    int max_exp = 0;
    while (x > 1) {
        int p = spf[x]; // 获取 x 的最小质因子
        int current_exp = 0;
        // 计算质因子 p 的指数
        while (x > 1 && x % p == 0) {
            current_exp++;
            x /= p;
        }
        // 更新最大指数
        if (current_exp > max_exp) {
            max_exp = current_exp;
        }
    }
    return max_exp;
}
void solve() {
    int n, k;
    std::cin >> n >> k;
    std::vector<int> a(n);
    std::vector<long long> weights(n);
    for (int i = 0; i < n; ++i) {
        std::cin >> a[i];
        weights[i] = get_weight(a[i]);
    }
    // 计算重量数组的前缀和
    std::vector<long long> prefix_sum(n + 1, 0);
    for (int i = 0; i < n; ++i) {
        prefix_sum[i + 1] = prefix_sum[i] + weights[i];
    }
    long long max_weight_sum = 0;
    // 使用单调队列寻找长度不超过 k 的最大子段和
    std::deque<int> dq;
    dq.push_back(0); // 初始窗口,对应 P[0]
    for (int i = 1; i <= n; ++i) {
        // 移除滑出窗口的队首索引
        if (!dq.empty() && dq.front() < i - k) {
            dq.pop_front();
        }
        // 计算以 i 结尾的子数组的最大和,并更新全局最大值
        // P[dq.front()] 是当前窗口内最小的前缀和
        max_weight_sum = std::max(max_weight_sum, prefix_sum[i] - prefix_sum[dq.front()]);
        // 维护队列的单调性(对应的前缀和值单调递增)
        while (!dq.empty() && prefix_sum[dq.back()] >= prefix_sum[i]) {
            dq.pop_back();
        }
        dq.push_back(i);
    }
    std::cout << max_weight_sum << "\n";
}
int main() {
    // 提高 IO 效率
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    // 全局一次性预处理
    sieve();
    int T;
    std::cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}
Python
import sys
from collections import deque
# a_i 的最大值
MAX_A = 10000001
# spf[i] 存储 i 的最小质因子
spf = list(range(MAX_A))
# 使用筛法预处理所有小于 MAX_A 的数的最小质因子
def sieve():
    for i in range(2, int(MAX_A**0.5) + 1):
        # 如果 i 是一个质数
        if spf[i] == i:
            # 遍历 i 的倍数 j,并更新它们的最小质因子
            for j in range(i * i, MAX_A, i):
                if spf[j] == j:
                    spf[j] = i
# 计算数字 x 的重量
def get_weight(x):
    if x == 1:
        return 0
    max_exp = 0
    while x > 1:
        p = spf[x] # 获取 x 的最小质因子
        current_exp = 0
        # 计算质因子 p 的指数
        while x > 1 and x % p == 0:
            current_exp += 1
            x //= p
        # 更新最大指数
        if current_exp > max_exp:
            max_exp = current_exp
    return max_exp
def solve():
    n, k = map(int, sys.stdin.readline().split())
    a = list(map(int, sys.stdin.readline().split()))
    weights = [get_weight(num) for num in a]
    # 计算重量数组的前缀和
    prefix_sum = [0] * (n + 1)
    for i in range(n):
        prefix_sum[i + 1] = prefix_sum[i] + weights[i]
    max_weight_sum = 0
    # 使用单调队列寻找长度不超过 k 的最大子段和
    dq = deque([0]) # 初始窗口,对应 P[0]
    for i in range(1, n + 1):
        # 移除滑出窗口的队首索引
        if dq and dq[0] < i - k:
            dq.popleft()
        
        # 计算以 i 结尾的子数组的最大和,并更新全局最大值
        max_weight_sum = max(max_weight_sum, prefix_sum[i] - prefix_sum[dq[0]])
        # 维护队列的单调性
        while dq and prefix_sum[dq[-1]] >= prefix_sum[i]:
            dq.pop()
        dq.append(i)
        
    print(max_weight_sum)
# 全局一次性预处理
sieve()
# 读取测试用例数量
T = int(sys.stdin.readline())
for _ in range(T):
    solve()
Java
import java.io.*;
import java.util.*;
public class Main {
    // a_i 的最大值
    static final int MAX_A = 10000001;
    // spf[i] 存储 i 的最小质因子
    static int[] spf = new int[MAX_A];
    // 使用筛法预处理所有小于 MAX_A 的数的最小质因子
    static void sieve() {
        for (int i = 0; i < MAX_A; i++) {
            spf[i] = i;
        }
        for (int i = 2; i * i < MAX_A; i++) {
            // 如果 i 是一个质数
            if (spf[i] == i) {
                // 遍历 i 的倍数 j,并更新它们的最小质因子
                for (int j = i * i; j < MAX_A; j += i) {
                    if (spf[j] == j) {
                        spf[j] = i;
                    }
                }
            }
        }
    }
    // 计算数字 x 的重量
    static int getWeight(int x) {
        if (x == 1) return 0;
        int maxExp = 0;
        while (x > 1) {
            int p = spf[x]; // 获取 x 的最小质因子
            int currentExp = 0;
            // 计算质因子 p 的指数
            while (x > 1 && x % p == 0) {
                currentExp++;
                x /= p;
            }
            // 更新最大指数
            if (currentExp > maxExp) {
                maxExp = currentExp;
            }
        }
        return maxExp;
    }
    static void solve(BufferedReader br) throws IOException {
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int k = Integer.parseInt(st.nextToken());
        int[] a = new int[n];
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i < n; i++) {
            a[i] = Integer.parseInt(st.nextToken());
        }
        
        long[] weights = new long[n];
        for (int i = 0; i < n; i++) {
            weights[i] = getWeight(a[i]);
        }
        // 计算重量数组的前缀和
        long[] prefixSum = new long[n + 1];
        prefixSum[0] = 0;
        for (int i = 0; i < n; i++) {
            prefixSum[i + 1] = prefixSum[i] + weights[i];
        }
        long maxWeightSum = 0;
        // 使用单调队列寻找长度不超过 k 的最大子段和
        Deque<Integer> dq = new ArrayDeque<>();
        dq.addLast(0); // 初始窗口,对应 P[0]
        for (int i = 1; i <= n; i++) {
            // 移除滑出窗口的队首索引
            if (!dq.isEmpty() && dq.peekFirst() < i - k) {
                dq.pollFirst();
            }
            // 计算以 i 结尾的子数组的最大和,并更新全局最大值
            maxWeightSum = Math.max(maxWeightSum, prefixSum[i] - prefixSum[dq.peekFirst()]);
            // 维护队列的单调性
            while (!dq.isEmpty() && prefixSum[dq.peekLast()] >= prefixSum[i]) {
                dq.pollLast();
            }
            dq.addLast(i);
        }
        System.out.println(maxWeightSum);
    }
    public static void main(String[] args) throws IOException {
        // 全局一次性预处理
        sieve();
        
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = Integer.parseInt(br.readLine());
        while (T-- > 0) {
            solve(br);
        }
    }
}
        题目内容
一个大于 1 的正整数 x 的重量定义为:将其分解质因数之后得到的最大的指数。例如,90=21×32×51 ,它的重量为 max{1,2,1}=2 。
现在,给定 n 个整数 a1,a2,…,an ,小歪想找到这样不超过 k 个连续的位置,满足:它们上面数字的重量之和是最大的。你只需要输出这个最大的重量之和即可。
输入描述
每个测试文件均包含多组测试数据。第一行输入一个整数 T(1≦T≦2×105) 代表数据组数,每组测试数据描述如下:
第一行输入两个整数 n,k(1≦k≦n≦2×105) ,代表整数序列的长度、最多选择连续整数的个数。
第二行输入 n 个整数 a1,a2,...,an(2≦ai≦107) ,代表整数序列。
除此之外,保证单个测试文件的 n 之和不超过 2×105 。
输出描述
对于每组测试数据,新起一行。输出一个整数,代表最大的重量之和。
样例1
输入
3
7 3
4 6 5 8 1 3 2 9
5 5
2 2 2 2 9
2 1
6 8
输出
5
6
3
说明
对于第一组数据,各个数字的重量依次为:2,1,1,3,1,1,2 。选择区间 [3,5] (下标从 1 开始计)即可。