前缀和取模-数列 纪中集训
不难想到,连续的子序列暗示我们前缀和。
然而单纯的前缀和是
O
(
n
2
)
O(n^2)
O(n2)的,肯定跑不过。
那怎么办呢?
问题要求我们求区间和为k的倍数的区间个数。
显而易见,如果两个数模k是同一个数,那么他们的差模k余0。
所以我们统计前缀和为同一个数的个数,用他们组合一下都满足条件(不是排列 因为要大减小)
c
[
n
]
[
2
]
=
n
∗
(
n
−
1
)
2
c[n][2]=\frac{n*(n-1)}{2}
c[n][2]=2n∗(n−1)
c
n
t
[
0
]
cnt[0]
cnt[0]要初始成1,因为余数为0不需要减别的前缀和,可以单独。为方便起见,假设在前面已经放了一个余数为0的,让它减。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXN 50005
#define MAXK 1000005
#define INF 0x3f3f3f3f
#define LL long long
int n,k;
int cnt[MAXK];
LL Abs(LL x)
{
if(x>=0) return x;
return -x;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(cnt,0,sizeof(cnt));
LL ans=0;
cnt[0]=1;//余数为0可以单独 假设在前面放一个余数为0的,就和其它情况一样
scanf("%d %d",&k,&n);
LL s=0,x;
for(int i=1;i<=n;i++)
{
scanf("%lld",&x);
if(x<0) {LL tp=Abs(x)/k;x+=tp*k;}
while(x<0) x+=k;
s=(s+x)%k;
int tmp=s;
//printf("%d\n",tmp);
cnt[tmp]++;
}
for(int i=0;i<k;i++)
if(cnt[i]>=2)
ans+=1LL*cnt[i]*(cnt[i]-1)/2;
printf("%lld\n",ans);
}
return 0;
}