K倍区间(前缀和)

问题 E: 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

思路:所有输入值a[i]=(a[i]+a[i-1])%k,即得(1,i)区间对k的余数。

例如案例:

        1       2        3        4        5 取余后为:

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

a[1]+a[2]%k=1,说明a[2]一定能被k整除;

a[3]=0,即((a[1]+a[2])%k+a[3])%k=0,说明a[1]到a[3]之和肯定能被k整除,即(1,3)肯定能构成k倍区间;

同理a[4]=0,说明a[1]到a[4]之和肯定能被k整除,即(1,4)肯定能构成k倍区间,并且上一个a[i]=0到a[4]=0存在一个k倍区间(i+1,4)即(4,4)(此时cnt[0]=2,ans+=cnt[0]);

a[5]=1,由于之前存在a[1]=1,a[2]=1,所以(2,5),(3,5)肯定能构成k倍区间(此时cnt[1]=2);

得出这样的规律:

1.a[i]!=0时

所有输入值经过a[i]=(a[i]+a[i-1])%k运算后,a[i]=x,当再次出现a[j]=x,a[k]=x时,(i+1,j),(i+1,k),(j+1,k)均为k倍区间,随着a[i]=x次数的增加,cnt[x]++(因为a[i]与之前的x值也可得到k倍区间)

(初始化cnt[x]=0)

2.a[i]==0,a[j]==0时

说明(1,i),(1,j),(i+1,j)均为k倍区间,且随着a[i]=0次数的增加,cnt[0]++(因为a[i]与之前的0值也可得到k倍区间)

(初始化cnt[0]=1)

/...菜鸡的一点学习成果,欢迎大佬们指正错误.../

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<map>
#include<vector>
#include<stack>
#include<queue>
#include<set>
#define mod 998244353;
#define Max 0x3f3f3f3f;
#define Min 0xc0c0c0c0;
#define mst(a) memset(a,0,sizeof(a))
#define f(i,a,b) for(int i=a;i<b;i++)
using namespace std;
typedef long long ll;
const int maxn=100005;
int n,k;
int arr[maxn],cnt[maxn];//cnts数组记录每个余数的出现次数
int main(){
    ios::sync_with_stdio(false);
    while(cin>>n>>k){
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++){
            cin>>arr[i];
            arr[i]=(arr[i]+arr[i-1])%k;//记录(1,i)所有区间的对k取余值
        }
        cnt[0]=1;//第一次出现a[i]=0,(1,i)即为k倍区间
        ll ans=0;
        for(int i=1;i<=n;i++){
            ans+=cnt[arr[i]];    //cnt[i]表示每个余数之前的出现次数
            cnt[arr[i]]++;       //之前出现过则可以构成k倍区间,用ans来累加方案数
        }
        cout<<ans<<endl;
    }
    return 0;
}



猜你喜欢

转载自blog.csdn.net/qq1013459920/article/details/82764547