k倍区间
时间限制:2.0s 内存限制:256.0MB
问题描述
给定一个长度为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
朴素做法【暴力枚举(TLE)】
#include <bits/stdc++.h>
using namespace std;
int n,k,ans,cnt,sum[100010];
int main()
{
cin>>n>>k;
sum[0]=0;
for(int i=1; i<=n; i++)
{
int a;
cin>>a;
sum[i] = (sum[i-1]+a) %k; //前缀和
}
for(int i=1; i<=n; i++)
for(int j=0; j<i; j++)
{
cnt = sum[i]-sum[j];
if(cnt%k==0) ans++;
}
cout<<ans;
return 0;
}
思路1:前缀和+组合数求解
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll N, K, ans=0;
ll sum[100010];
ll cnt[100010];
int main()
{
cin>>N>>K;
memset(cnt, 0, sizeof(cnt));
sum[0]=0;
for(int i=1; i<=N; i++)
{
int a;
cin>>a;
//前缀和
sum[i] = (sum[i-1] + a)%K;
cnt[sum[i]]++;
}
//情况一
ans += (cnt[0]*(cnt[0]+1))/2;
//情况二
for(int i=1; i<K; i++)
ans += (cnt[i]*(cnt[i]-1))/2;
cout<<ans;
return 0;
}
思路2:前缀和+DP求解
我们首先要我们是要求出任意长度的区间和 符合是k的倍数的 区间个数 这样我们很容易得到 (sum[i]-sum[j]) %k == 0
接着我们可以得到 (sum[i]%k-sum[j]%k) %k == 0
因为前缀和都为正整数 所以我们可以推导出 sum[i]%k==sum[j]%k
所以我们只需要找到前缀和对k求余结果相同的两个点 他们之间的区间即为k倍区间。
int main()
{
// ios::sync_with_stdio(false);
scanf("%d%d",&n,&k);
ll ans=0;
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
sum[i]=(sum[i-1]+a[i]) %k; //求前缀和对k求余
if(sum[i]==0) {
//自己也是k倍区间
++dp[sum[i]];
ans+=(dp[sum[i]]);
}
else {
//和前面余数相同的点组成k倍区间
ans+=(dp[sum[i]]);
++dp[sum[i]];
}
}
printf("%lld\n",ans);
return 0;
}