第八届蓝桥杯C++B组: k倍区间

版权声明:转载留名即可 ^_^ https://blog.csdn.net/qq_33375598/article/details/88651724

给定一个长度为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




程序应该输出:
6

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

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

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

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

思路:

1,暴力前缀和:枚举i和j(j大于等于i),从1到n,从前往后算区间和等于k的个数(但是两个for循环,在n等于100000时,O(N^2)会超时)【维护一个区间:前缀和(静态数据)、树状数组、区间数,因为数组位静态数组,所以选择前缀和;

参考代码:】

2,优化前缀和:求区间和%k的余数(s[i]),利用同余做差是模的倍数,然后就可以统计不同s[i]的个数cnt[s[i]],然后从相同的cnt[s[i]]选择任意两个就可以组成一个K倍区间。

理解:题目中的

5 2




中有6个区间,就是【1,2,3】【1,2,3,4】【2,3,4,5】【2】【3,4,5】【4】

序号    0  1   2    3    4   5

        a      1   2   3    4   5 

        s  0   1  3   6   10  15

        s'  0  1   1   0    0    1

其中:s[i ] = 求和0~i的ai的和

si[i] = s[i] % k 

因为:

区间和%k余数为1的有:(3个选2,即C3 2)

s'2-s'1-----【2】

s'5 - s‘1 ------【2,3,4,5】

s‘5 - s‘2------【3,4,5】

区间和%k余数为0的有:(3个选2个,即C3 2)

s‘3 - s'0:-----【1,2,3】

s‘4 - s’0:----【1,2,3,4】

s‘4 - s’3:-----【4】

参考代码1(暴力):

#include <cstdio>

const int maxn = 100005;
int a[maxn];
int s[maxn];//前缀和
int main(){
    int n, k;
    scanf("%d%d", &n, &k);

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

    long long ans = 0;
    for (int i = 1; i <= n; ++i) {//枚举i,j
        for (int j = i; j <= n; ++j) {
            //i,j之间的区间和 = s[j] - s[i -1]
            if((s[j] - s[i -1]) % k == 0){
                ans++;
            }
        }
    }
    printf("%lld", ans);
    return 0;
}

参考代码2:(优化——同余做差是模的倍数)

#include <cstdio>
#include <map>

using namespace std;

const int maxn = 100005;
int a[maxn];
int s[maxn];//前缀和
map<int, int> cnt;//同余的个数统计

int main(){
    int n, k;
    scanf("%d%d", &n, &k);
    cnt[0] = 1;
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        s[i] = (s[i - 1] + a[i]) % k;
        cnt[s[i]]++;
    }

    long long ans = 0;
    for (int j = 0; j < k; ++j) {//余数必然在0~k-1之间
        ans += (long long)cnt[j] * (cnt[j] -1) / 2;//例如所有前缀和中%k=3有三个,那么它们任意选2可得一个k倍区间,C32
    }

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

猜你喜欢

转载自blog.csdn.net/qq_33375598/article/details/88651724