蓝桥杯 历届试题 k倍区间(同余定理、前缀和、组合)

标题: k倍区间

给定一个长度为N的数列,A1, A2, … AN,如果其中一段连续的子序列Ai, Ai+1, … Aj(i <= j)之和是K的倍数,我们就称这个区间[i, j]是K倍区间。

你能求出数列中总共有多少个K倍区间吗?

输入
第一行包含两个整数N和K。(1 <= N, K <= 100000)
以下N行每行包含一个整数Ai。(1 <= Ai <= 100000)

输出
输出一个整数,代表K倍区间的数目。
例如,
输入:
5 2
1
2
3
4
5

程序应该输出
6

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 2000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

注意:
main函数需要返回0;
只使用ANSI C/ANSI C++ 标准;
不要调用依赖于编译环境或操作系统的特殊函数。
所有依赖的函数必须明确地在源文件中 #include
不能通过工程设置而省略常用头文件。

提交程序时,注意选择所期望的语言类型和编译器类型。

ps:这题如果直接枚举各个起点和终点的话肯定会TLE的,因此需要想办法将复杂度降低。
我们用数组 sum[i] s u m [ i ] 表示数组 data[0]+data[1]+...+data[i] d a t a [ 0 ] + d a t a [ 1 ] + . . . + d a t a [ i ] 的和。那么区间 [i,j] [ i , j ] 的和为 sum[j]sum[i1] s u m [ j ] − s u m [ i − 1 ] 。如果区间 [i,j] [ i , j ] k k 的倍数,有 (sum[j]sum[i1])%k==0 ( s u m [ j ] − s u m [ i − 1 ] ) % k == 0 ,由同余定理 ( 给定一个正整数m,如果两个整数a和b满足a - b能够被m整除,即 (ab)/m ( a − b ) / m 得到一个整数,那么就称整数a与b对模m同余,记作 ab (mod m) a ≡ b   ( m o d   m ) ), 得 sum[j]%k==sum[i1]%k s u m [ j ] % k == s u m [ i − 1 ] % k 。 那么接下来我们用一个数组cnt[i]来储存sum数组取模 k 之后所有等于i的个数。最后用组合公式就可以得到最终的结果了。
下图是根据样例做出的解题过程和解析
蓝桥杯 历届试题 k倍区间(同余定理、前缀和、组合)_第1张图片
为什么结果是6呢?我们可以简单地枚举一下这6种情况:
蓝桥杯 历届试题 k倍区间(同余定理、前缀和、组合)_第2张图片
我们可以从 cnt[0]=2 c n t [ 0 ] = 2 cnt[1]=3 c n t [ 1 ] = 3 来得到上面的这几种情况, cnt[0]=2 c n t [ 0 ] = 2 来自 sum[2]%2 s u m [ 2 ] % 2 sum[3]%3 s u m [ 3 ] % 3 ,由上方的结论如果 sum[i]% k==sum[j]% k s u m [ i ] %   k == s u m [ j ] %   k , 则区间 [i+1,j] [ i + 1 , j ] 是k的倍数。所以 sum[2]%2 s u m [ 2 ] % 2 sum[3]%3 s u m [ 3 ] % 3 可以推得区间 [3,3] [ 3 , 3 ] 是k的倍数,同理根据其他的数据我们可以推出区间 [0,2],[2,4],[0,3],[1,4] [ 0 , 2 ] , [ 2 , 4 ] , [ 0 , 3 ] , [ 1 , 4 ] 是k的倍数。但是cnt[0]比较特殊为什么呢?因为 cnt[0]=cnt[sum[i]%k=0] c n t [ 0 ] = c n t [ s u m [ i ] % k = 0 ] ,它从起始位置到i 所构成的区间本身就是k倍区间,因此cnt[0]要加上1,参与组合。

#include
#include
#include
using namespace std;
const int maxn = 1e5+10;
int data[maxn];
long long sum[maxn];
long long cnt[maxn];
int main()
{
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    int n, k;
    while(cin>>n>>k)
    {
        memset(sum, 0, sizeof sum);
        memset(data, 0, sizeof data);
        cin>>data[0];
        sum[0] = data[0] % k;
        for(int i = 1; i < n; i++) {
            cin>>data[i];
            sum[i] = (sum[i - 1] + data[i]) % k;//前缀和
        }
        cnt[0]++;
        for(int i = 0; i < n; i++)
        {//进行统计
            cnt[sum[i]]++;
        }
        long long ans = 0;
        for(int i = 0; i < n; i++)
        {
            ans += (1 + (cnt[i] - 1))*(cnt[i] - 1) / 2;//组合公式
        }
        cout<return 0;
}

附上该题目的测试数据(打开方式建议用写字板,避免出现乱码), (测试数据来源于网络)(密码:g8ui)

你可能感兴趣的:(蓝桥杯,数学分析)