k 倍区间(同余定理,组合数)

1|0题目描述

给定一个长度为 N 的数列,1,2,⋯A1,A2,AN,如果其中一段连续的子序列 ,+1,⋯(≤)Ai,Ai+1,Aj(ij) 之和是 K 的倍数,我们就称这个区间 [,][i,j] 是 K 倍区间。

你能求出数列中总共有多少个 K 倍区间吗?

2|0输入格式

第一行包含两个整数 N 和 K(1≤,≤105)(1N,K105)。

以下 N 行每行包含一个整数 Ai(1≤≤105)(1Ai105)。

3|0输出格式

输出一个整数,代表 K 倍区间的数目。

4|0输入输出样例

输入 #1
5 2 1 2 3 4 5
输出 #1
6
本题的第一思路就前缀和,本以为时间限制是2s完全够我开n2,但还是tle了,想不到优化的方案
没想到使用数学知识来优化:
利用同余定理:当 a mod k=b mod k 时,abmodk=0。
若设前缀和数组为 s,则我们知道,si 就表示 0+1+2+...+a0+a1+a2+...+ai
那么我们用另一个数组存储每个前缀和的模数,当找到相同模数时,中间的那段即为 k 的倍数。
统计前缀和除以 k 后余数的数量,然后两两组合计算答案。
其实就是计算 ∑=0−1[]2i=0k1Ccnt[i]2​,利用组合数两两组合;
然后 00 的数量要特判一下。
比如计算区间 [1,5][1,5] 的和,应该是 sumd5sumd0,所以 sumd0 也要统计进去,而 sumd0=0
所以特别处理一下就可以了:
#include<bits/stdc++.h> using namespace std; const int N=1e5+10; long long n,k,a[N],s[N],res,sum; int main() { cin>>n>>k; for(int i=1;i<=n;i++){ cin>>a[i]; sum=(sum+a[i])%k; s[sum]++;//利用桶来储存数量 } s[0]++;//这里的0要特判一下,毕竟前面 for(int i=0;i<k;i++) res+=s[i]*(s[i]-1)/2;//组合数两两组合公式 cout<<res; return 0; }

还有另外一种不适用组合数的方法:

每次取这个数组的前缀和与 k 取模,并加上这个前缀和出现的次数。如果不止一次出现,那么上一次出现的位置到这一次出现的位置之间的数的和就一定可以被 k 整除,即为 k 倍区间。

另外,如果这个前缀和已经可以直接被 k 整除的话,也算 k 倍区间。

输出最后的个数即可:

 

#include<bits/stdc++.h> using namespace std; const int N=1e5+10; long long a[N],res,s[N],n,k,ans[N]; int main() { cin>>n>>k; for(int i=1;i<=n;i++){ cin>>a[i]; s[i]=(s[i-1]+a[i])%k; res+=ans[s[i]];//解释一下这里为什么要先加再++ //因为同余定理是两个同余的数合并才是一个k倍区间 //模拟一下,如果先++再加的话,此时余这个数的个数只有一个,不能算入k倍区间; ans[s[i]]++; } cout<<res+ans[0]; }

__EOF__

本文作者Sakurajimamai
本文链接https://www.cnblogs.com/o-Sakurajimamai-o/p/17472554.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   o-Sakurajimamai-o  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
-- --
点击右上角即可分享
微信分享提示