AcWing 1230. K倍区间

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

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

输入格式

第一行包含两个整数 N 和 K。

以下 N 行每行包含一个整数 Ai。

输出格式

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

数据范围

1≤N,K≤100000,
1≤Ai≤100000

输入样例:

5 2
1
2
3
4
5

输出样例:

6

思路分析

算法角度:通过区间和想到可以用前缀和.
如果我们想遍历所有的区间,可以用两个for循环:

for(int r=1;r<=n;r++)
	for(int l=1;l<=r;l++)
		ans=s[r]-s[l-1];

转化一下:

for(int r=1;r<=n;r++)
	for(int l=0;l<r;l++)
		ans=s[r]-s[l];

从这个双重for循环中我们能发现什么:当我们求得前缀和数组后(从0-n),在0-n中任取两个不同的数i,j(i<j),s[j]-s[i]即为一个区间和,当我们取完所有可能性的时候,我们就构成了所有区间的可能。
那么我们求完前缀和以后,区间和是k的倍数就可以转化为某两个前缀和之差为k的倍数,也就是说s[j]%k==s[i]%k,所以问题就迎刃而解:
1.统计前缀和数组中各个数之于k的余数
2.计算,相加
代码如下:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll maxn=1e5+10;

ll n,k;
ll a[maxn],s[maxn];

int main(){
	scanf("%lld %lld",&n,&k);
	for(ll i=1;i<=n;i++)	scanf("%lld",&a[i]),s[i]=s[i-1]+a[i];
	ll ans=0;
	map<ll,ll> M;
	for(ll i=1;i<=n;i++){
		ll t=s[i]%k;
		M[t]++;
	}
	ans+=M[0];
	for(map<ll,ll>::iterator it=M.begin();it!=M.end();it++){
		ll t=it->second;
		ans+=(t-1)*t/2;
	}
	printf("%lld",ans);
	return 0;
}

一般角度: 首先想到O(n^3)的算法:

for(int r=1;r<=n;r++)
	for(int l=1;l<=r;l++){
		int sum=0;
		for(int k=l;k<=r;k++)
			sum+=a[k];
		if(sum%k==0)	ans++;
	}

利用前缀和优化:

for(int r=1;r<=n;r++)
	for(int l=1;l<=r;l++){
		int sum=s[r]-s[l-1];
		if(sum%k==0)	ans++;
	}

考虑下边这个for循环,可以怎样转化?对,就是s[r]和s[l-1]对k取余的余数相同。所以内层for循环的作用就是判断i从0到r-1有多少个s[i]和s[r]的余数相同,因此我们可以用一个数组记录下来。

cnt[i]表示余数为i的数有多少个
int ans=0;
for(int r=1;r<=n;r++){
	ans+=cnt[r%k];
	cnt[r%k]++;
}

AC代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll maxn=1e5+10;

ll n,k;
ll a[maxn],s[maxn];
ll ans=0,cnt[maxn];

int main(){
	scanf("%lld %lld",&n,&k);
	for(ll i=1;i<=n;i++)	scanf("%lld",&a[i]),s[i]=s[i-1]+a[i];
	cnt[0]=1;
	for(int r=1;r<=n;r++){
		ans+=cnt[s[r]%k];
		cnt[s[r]%k]++;
	}
	printf("%lld",ans);
	return 0;
}

关键点

(a[l]+a[l+1]+…+a[r])%k==0 <=> ( s[r]-s[l-1])%k ==0 < = > s[r] %k ==s[l-1]%k

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值