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 <xxx>
  不能通过工程设置而省略常用头文件。

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

题解

题目清晰明了,简单的,暴力枚举~~~

 1 void fun(int *a, int n, int k)
 2 {
 3     int c = 0;
 4     for(int i=0;i<n;i++){
 5         for(int j=i;j<n;j++){
 6             int sum = 0;
 7             for(int t=i;t<j;t++){
 8                 sum += a[t];
 9             }
10             if(!(sum%k)){
11                 c++;
12             }
13         }
14     }
15     cout<<c<<endl;
16  } 

 或

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<cmath>
 5 
 6 using namespace std;
 7 
 8 typedef long long ll;
 9 
10 const int MAX = 100001;
11 
12 int main()
13 {
14     int n, k;
15     cin>>n>>k;
16     ll a[MAX], b[MAX], s = 0;
17     memset(a, 0, sizeof(a));
18     memset(b, 0, sizeof(a));
19     ll c = 0;
20     for(int i=1;i<=n;i++){
21         cin>>a[i];
22         if(a[i]%k == 0)
23             c++;
24         s += a[i];
25         b[i] += s;
26     }
27     b[0] = 0;
28     for(ll i=0;i<n-2;i++){
29         for(ll j=i+2;j<=n;j++){
30             int e = abs(b[j] - b[i]);
31             if(e%k == 0)
32                 c++;
33         }
34     }
35     cout<<c<<endl;
36     
37     return 0;
38 }

 

然后,超时了~~~

题目部分数据很大,枚举的时间复杂度为o(n^3)或o(n^2),超时还是很正常的!

在网上学习了很多篇文章之后,总算是明白了优化算法。

现将我的理解分享给大家!

设有一数列a,需要求的是k=2的k倍区间:

 a = [1, 2, 3, 4, 5] 

我们可以知道

 任何区间[i, j],都可以表示为:前 j 项的和减去前 i-1 项的和,即 sum = S[j] - S[i-1] 

如果这个区间是k倍区间,那么下列等式成立:

1 (S[j] - S[i-1])%k == 0
// 移项得 2 S[j]%k == S[i-1]%k

 也就是说,我们只需要求出前n项和,即:
 S[1], S[2], ..., S[n] 

然后我们判断它们对k取模的值,

  [1, 1, 0, 0, 1] 

此时,我们只要随意的选取模相同的区间,那么就是满足题意的区间,但是当我们的 i 取0时,会有-1出现,因为我们的公式是

  S[j]%k == S[i-1]%k 

所以我们要在最前面添加一个值,但是又不能影响整个数列,很明显,我们要添加一个 0, 如下图:

图不是很好看,但是大致就是这意思!!!

换句话说,我们只需要求出相同的数的组合数就ok了。


 

解 

 1 #include<iostream>
 2 #include<cstring>
 3 
 4 using namespace std;
 5 
 6 typedef long long ll;
 7 
 8 const int MAX = 100009;
 9 int n, k;
10 ll a[MAX], s[MAX], c = 0;
11 
12 int main()
13 {
14     
15     cin>>n>>k;
16     memset(a, 0, sizeof(a));
17     memset(s, 0, sizeof(a));
18     int x = 0, y = 0;
19     s[0] = 1;
20     for(int i=0;i<n;i++){
21         cin>>x;
22         y = (y + x)%k;
23         s[y%k]++;
24     }
25     for(int i=0;i<k;i++){
26         c += (s[i]*(s[i]-1))/2;
27     }
28     cout<<c<<endl;
29     return 0;
30 }
View Code

此外,我还看到另一种解法,可惜有一小地方没弄明白,这里放上代码(https://www.lucien.ink/archives/63/)

 

 1 #include <cstdio>
 2 
 3 int tmp, sum, cnt[100007], n, k;
 4 
 5 long long ans;
 6 
 7 int main() 
 8 {
 9     scanf("%d%d", &n, &k);
10     for (int i = 1; i <= n; i++){
11         scanf("%d", &tmp);
12         sum = (sum + tmp) % k;
13         ans += cnt[sum]++;
14     }
15     
16     prisntf("%lld\n", ans + cnt[0]);
17     return 0;
18 }
View Code

 

posted @ 2018-11-17 15:54  maybeTang  阅读(520)  评论(0编辑  收藏  举报