一【题目描述】
标题: 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>
不能通过工程设置而省略常用头文件。
提交程序时,注意选择所期望的语言类型和编译器类型。
二【解题思路】
看到k倍区间的标题,就想到了最大子序列和最大子串,可能会有关联,比较也都可以用循环判断。所以读完题目之后想到可以用枚举,for循环从第一个数字一直到最后一个数字,内层的for循环则进行累加判断,满足条件加一,判断下一个,不满足则退出,继续从第二个数字开始循环判断。看了一下数据规模,这样做的话应该要考虑优化才可以通过。但是对于查找优化(二分这些都不清楚)。
#include<iostream>
using namespace std;
int main()
{
int n,k;
int a[100000];
cin>>n>>k;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
int sum=0;
int ans = 0;
for(int i=0;i<n;i++)
{
if(a[i]%k==0) ans++;//首先判断a[i]是不是,如果是,那么加一;
sum = a[i];
for(int j=i+1;j<n;j++)
{
sum +=a[j];
if(sum%k==0)
{
ans++;
}
}
}
cout<<ans<<endl;
return 0;
}
但是上面的代码只可以解决一部分的数据规模的问题,所以我们考虑优化,我们可以考虑前缀和,在赋值的时候就可以添加前缀和,这样在for循环里面就不用重复的去计算求和的这一部分。可是这样的优化还是不够的,所以我们考虑对前缀和进行k取模操作,然后可以统计相同的余数的个数有多少,然后这样余数相同的和两两相减,肯定是k的倍数。比如5%2=1,7%2=1,那么(7-2)%2==0.所以我们开始码代码吧。
三【解题步骤】
#include<iostream>
#include<map>//这里用map记录余数相同的个数
using namespace std;
int a[100000];//录入的数字
int s[100000];//保存前缀和
map<int,int>cnt;
int main()
{
int n,k;
cin>>n>>k;//处理输入
s[0]=0;
cnt[0]=1;//这里s[0]等于0,那么肯定存在一个数余数是0,所以这里初始化为1
for(int i=1;i<=n;i++)
{
cin>>a[i];
s[i]=(s[i-1]+a[i])%k;//处理前缀和
cnt[s[i]]++;//将相同余数添加进入map容器
}
long long ans = 0;
for(int i=0;i<k;i++)
{
ans+=(long long)cnt[i]*(cnt[i]-1)/2;//这里相同的余数进行两两的组合,那么肯定是组合数C(2,cnt[i])
}
cout<<ans<<endl;//输出结果
return 0;
}
四【总结】
当我们有一个一个想法的时候尽量要考虑到时间的优化,这里首先前缀和优化,后面有将内层for循环用排列组合公式优化,最后就得到了符合的代码。如有更好的代码,欢迎交流哦。谢谢