把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

前缀和取模-数列 纪中集训



不难想到,连续的子序列暗示我们前缀和。
然而单纯的前缀和是 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(n1)
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;
}

posted @ 2019-08-01 21:15  Starlight_Glimmer  阅读(20)  评论(0编辑  收藏  举报  来源
浏览器标题切换
浏览器标题切换end