UVa 12220 Divisible Subsequences

题目描述

给定一个正整数序列,统计其中所有连续子序列(有时也称为子串,与子序列不同,子序列可以跳过某些元素)的和能被给定整数整除的数量。这些子序列可以重叠。

例如,序列:
2,1,2,1,1,2,1,22, 1, 2, 1, 1, 2, 1, 22,1,2,1,1,2,1,2

包含 666 个和能被 444 整除的连续子序列:

  • 第一个到第八个数
  • 第二个到第四个数
  • 第二个到第七个数
  • 第三个到第五个数
  • 第四个到第六个数
  • 第五个到第七个数

输入格式

第一行是一个整数 ccc (1≤c≤2001 \leq c \leq 2001c200),表示测试用例的数量。

每个测试用例包含两行:

  • 第一行是两个整数 ddd (1≤d≤1,000,0001 \leq d \leq 1,000,0001d1,000,000) 和 nnn (1≤n≤50,0001 \leq n \leq 50,0001n50,000),分别表示用于整除的除数和序列的长度
  • 第二行是序列的元素,每个元素是介于 1111,000,000,0001,000,000,0001,000,000,000 之间的整数

输出格式

对于每个测试用例,输出一个整数,表示满足条件的连续子序列的数量。

题目分析

问题理解

我们需要找到所有连续子序列(子数组),使得这些子序列的元素和能够被给定的除数 ddd 整除。

最直观的解法是枚举所有可能的连续子序列,计算它们的和,然后检查是否能被 ddd 整除。然而,这种方法的时间复杂度为 O(n2)O(n^2)O(n2),对于 nnn 最大为 50,00050,00050,000 的情况来说是不可行的。

关键思路

利用前缀和同余定理来优化:

  1. 前缀和定义:设 prefixSum[i]prefixSum[i]prefixSum[i] 表示前 iii 个元素的和
  2. 子序列和:子序列 [i,j][i, j][i,j] 的和可以表示为 prefixSum[j]−prefixSum[i−1]prefixSum[j] - prefixSum[i-1]prefixSum[j]prefixSum[i1]
  3. 整除条件:如果子序列 [i,j][i, j][i,j] 的和能被 ddd 整除,那么:
    (prefixSum[j]−prefixSum[i−1])%d=0(prefixSum[j] - prefixSum[i-1]) \% d = 0(prefixSum[j]prefixSum[i1])%d=0
    根据同余定理,这等价于:
    prefixSum[j]%d=prefixSum[i−1]%dprefixSum[j] \% d = prefixSum[i-1] \% dprefixSum[j]%d=prefixSum[i1]%d

算法设计

基于上述观察,我们可以设计如下算法:

  1. 计算前缀和数组,但不需要存储所有前缀和,只需要维护当前前缀和
  2. 使用一个计数数组记录每个余数出现的次数
  3. 特别地,初始时 count[0]=1count[0] = 1count[0]=1,表示空前缀(和为 000,余数为 000
  4. 遍历序列,对于每个位置:
    • 更新当前前缀和
    • 计算当前前缀和模 ddd 的余数
    • 增加对应余数的计数
  5. 最后,对于每个余数 rrr,如果有 kkk 个前缀和的余数为 rrr,那么这些前缀和中任意两个之间的子序列都满足条件,数量为组合数 C(k,2)=k(k−1)2C(k, 2) = \frac{k(k-1)}{2}C(k,2)=2k(k1)

为什么这样工作?

  • 当两个前缀和 prefixSum[i]prefixSum[i]prefixSum[i]prefixSum[j]prefixSum[j]prefixSum[j] (i<ji < ji<j) 的余数相同时,它们之间的子序列 [i+1,j][i+1, j][i+1,j] 的和 prefixSum[j]−prefixSum[i]prefixSum[j] - prefixSum[i]prefixSum[j]prefixSum[i] 必然能被 ddd 整除
  • 初始的 count[0]=1count[0] = 1count[0]=1 保证了从序列开头开始的子序列也能被正确计数

时间复杂度

  • 每个测试用例的时间复杂度:O(n)O(n)O(n)
  • 总体时间复杂度:O(c×n)O(c \times n)O(c×n)
  • 空间复杂度:O(d)O(d)O(d)

这完全在题目约束范围内。

代码实现

// Divisible Subsequences
// UVa ID: 12220
// Verdict: Accepted
// Submission Date: 2025-11-29
// UVa Run Time: 0.150s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

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

int main() {
    int testCaseCount;
    cin >> testCaseCount;
    while (testCaseCount--) {
        int divisor, sequenceLength;
        cin >> divisor >> sequenceLength;
        vector<long long> prefixModCount(divisor, 0);
        prefixModCount[0] = 1;  // 初始前缀和 0 的余数为 0
        long long prefixSum = 0;
        for (int i = 0; i < sequenceLength; i++) {
            long long num;
            cin >> num;
            prefixSum += num;
            int remainder = prefixSum % divisor;
            prefixModCount[remainder]++;
        }
        long long result = 0;
        for (int i = 0; i < divisor; i++) {
            long long count = prefixModCount[i];
            result += count * (count - 1) / 2;  // 组合数 C(count, 2)
        }
        cout << result << endl;
    }
    return 0;
}

总结

本题的关键在于将问题转化为寻找余数相同的前缀和对。通过利用前缀和和同余定理,我们将原本 O(n2)O(n^2)O(n2) 的暴力解法优化到了 O(n)O(n)O(n) 的高效解法。这种技巧在处理与子序列和相关的问题时非常有用,特别是在涉及整除条件的情况下。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值