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

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