k倍區間(前綴和+組合)

前言

傳送門

正文

問題描述

給定一個長度爲N的數列,A1, A2, … AN,如果其中一段連續的子序列Ai, Ai+1, … Aj(i <= j)之和是K的倍數,我們就稱這個區間[i, j]是K倍區間。
你能求出數列中總共有多少個K倍區間嗎?

輸入格式

第一行包含兩個整數N和K。(1 <= N, K <= 100000)
以下N行每行包含一個整數Ai。(1 <= Ai <= 100000)

輸出格式

輸出一個整數,代表K倍區間的數目。

樣例輸入

5 2
1
2
3
4
5

樣例輸出

6

數據規模和約定

峯值內存消耗(含虛擬機) < 256M
CPU消耗 < 2000ms

注意事項

  • 本題就是求連續子序列的和爲k的倍數,這樣的子序列共有多少個,這邊需要注意數據規模爲10^5,因此直接用暴力枚舉一定會超時,
  • 該題可以優化靜態數的區間和,而前綴和就是優化靜態數(已經知道結果的數)區間和的一種方式,即一個數組A[100]有數1,2,3,4,5…,另一個數組S[100]有數1,3,6,10,15…,數組S中除了索引0之外的數,其他數S[ i ] = S[ i-1 ]+A[ i ],這樣的數組S就叫做前綴和數組。對於元素A[i]到A[j](以下均假設i<j)之間的序列和就等於S[j]-s[i]+A[i]。採用前綴和可以使得複雜度減少爲O(n2),然而這樣只能求得10^4的數,得不到滿分
  • 繼續優化,在前綴和的基礎上,對前綴和數組中每個數分別對K取餘,取餘後的數進行分類,由於是K倍區間,因此餘數爲0~K-1,而如果S[i]%K==S[j]%K,即二者餘數相同,那麼說明S[j]-S[i]的值一定爲K的倍數,而S[j]-S[i]=A[i+1]+…+A[j],也就說明存在一個序列滿足K倍序列。我們用一個yushu[i]表示前綴和數組S中所有對K取餘得到的餘數爲i的元素的個數。
    因此對yushu[i]進行組合Cm2(其中m=yushu[i]),故Cm2即爲K倍區間的數量,對yushu數組中每個元素的組合數進行求和,最後再加上yushu[0]的值則爲最終的K倍區間的數量,而爲什麼要加上yushu[0]的值呢?
    因爲yushu[0]的值就是表示前綴和數組S中的元素值剛好能夠整除K,假設是S[t]%K==0,也就是說從A[0]到A[t]的序列也是滿足K倍區間的,因此yushu[0]的值也是滿足K倍區間的序列的數量

參考題解

#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
using namespace std;
long long a[100010],sum[100010],yushu[100010]; 
int main(){
	long long n,k,count=0;
	scanf("%lld%lld",&n,&k);
	for(int i=0;i<n;i++){
		scanf("%lld",&a[i]);
		//sum爲前綴和數組,sum[i]表示數組a的前i+1項和 
		if(i==0)sum[i]=a[i];
		else sum[i]=sum[i-1]+a[i];
		//桶排序,yushu[i]表示sum[i]對k取餘的餘數爲i的個數 
		yushu[sum[i]%k]++;
	} 
	//進行組合數的計算 
	for(int i=0;i<k;i++){ 
		if(yushu[i]){
			count+=(yushu[i]*(yushu[i]-1)/2);	
		}
	}
	printf("%lld\n",count+yushu[0]);//注意最後需要加上yushu[0]的個數 
	return 0;
}

參考鏈接
https://juejin.im/post/5e7c29cf6fb9a00950602b5f

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章