P8649 [蓝桥杯 2017 省 B] k 倍区间

题目链接:https://www.luogu.com.cn/problem/P8649

方法一:模拟暴力(20分)

#include<bits/stdc++.h>
using namespace std;
const int max_n=100010;
int n, k, a[max_n];
long long ans, s;
int main()
{
	cin>>n>>k;
	for(int i=1; i<=n; i++)cin>>a[i];
	for(int i=1; i<=n; i++){
		s=0;
		for(int j=i; j<=n; j++){
			s=(s+a[j])%k;
			if(s==0)
				ans++;
		}
	}	
	cout<<ans;
	return 0;
 } 

方法二:前缀和

  • 图中的第一排[1, 2, 5, 4, 3]是A数组。绿色的第二排是前缀和,天蓝色的第三排是前缀和%K之后的结果,这里我们假设K=2。
  • 我们之前的算法,实际上就是在第三排的数组里,找两个相等的值。每一对相等的值,都对应着一个K倍区间。比如第三排有3个0,第一个0和第二个0,对应[1, 2, 5]这个区间;第一个和第三个0,对应[1, 2, 5, 4]这个区间;第二个和第三个0,对应[4]这个区间。
  • 所以我们已知第三排数组有3个0和3个1的情况下,可以直接用组合数求出来K倍区间的数目:C(3,2)+C(3, 2)=6。C(3, 2)是指从3个物品里取出2个来的组合数。因为有3个0和3个1,所以答案就是C(3, 2)+C(3, 2)。
#include<bits/stdc++.h>
using namespace std;
const int max_n=100010;
int n, k, a[max_n];
long long s[max_n];
long long ans, cnt[max_n];
int main()
{
	cin>>n>>k;
	for(int i=1; i<=n; i++)cin>>a[i];
	cnt[0]=1;
	//我们保存余数是X的前缀和有多少个,cnt[0]存储的是余数是0的前缀和有几个,cnt[1]存储的是余数是1的前缀和有几个,以此类推。 
	//初始值为cnt[s[0]%k]=1说前缀和里有1个0。
	for(int i=1; i<=n; i++){
		s[i] = (s[i-1] + a[i])%k;//计算出s[i]然后立刻%k求余数。
		cnt[s[i]]++;//把当前这个余数的出现次数累加。
		//在一边求前缀和,一边统计前缀和模K的余数0~(K-1)分别出现了多少次。第
	}
	for(int i=0; i<k; i++)//注意i从0到K-1,因为模K的余数最小是0,最大是K-1。模K余i的前缀和一共有cnt[i]个,所以我们从这cnt[i]个前缀中随便拿出两个就对应一个K倍区间。
		ans += cnt[i]*(cnt[i]-1)/2;//任意取两个均能相减为0 
	
	cout<<ans;
	return 0;
 } 
posted @ 2023-02-13 10:12  TFLSNOI  阅读(64)  评论(0编辑  收藏  举报