給定一個長度爲 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