目錄
1.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
2.題是什麼?
題意明瞭,讓你統計在所給長度1e5的數組中,區間和爲k的倍數的子區間數目
3.思路
首先,最簡單的思路,預處理前綴和,然後做二重循環對將近n*n個子區間一一求餘判斷這個子區間是否爲0,這樣的複雜度爲O(n^2),會爆.
其次,我的思路,既然我只是要知道區間和爲k倍數的子區間數目而不是到底有哪些子區間,那我豈不是只需要對這些子區間做一個記憶化,保留下對我後續計算有用的特徵就好了嗎,那麼一個區間的什麼特徵對我後續判斷其它區間區間和是否能被k整除有決定性作用呢?
tips:(記憶化是什麼? 我的理解:記憶化就是以某種方式存儲你計算過的數據,後面加以利用,優化計算效率)
自然是這個區間求餘k後的結果!
現在,我們知道能通過這個特徵去記憶化壓縮數據之後,我們該怎麼寫進算法裏並優化統計方法呢?
自然是做一個前綴和結果求餘k的數組sum和一個記憶化數組num,記錄之前出現過的所有前綴和求餘k的結果的情況,這樣的話之後當我們統計以a[i]結尾的區間有多少個滿足題意時,num[sum[i]]即爲答案!,爲什麼呢? 因爲你想如果k=5,sum[i]=3,以a[i]爲結尾滿足題意的情況數目不就是前面的sum中爲3的個數嗎,我這一整個區間多了3,那我丟掉某個正好多了3的前綴不就不多了嗎!
正好就是我們的num[3],爲了維護num數組,每次要num[sum[i]]+=1,將本次情況更新進記憶化數組.
4.知識精煉
有沒有注意到爲什麼num被叫做記憶化數組?因爲他裏面存的某個數字比如num[2]=3的含義是,前綴和求餘結果爲2的情況當前有3種,他是一種對於過去所有情況的記憶,僅僅根據最核心的特徵進行記憶,這就是記憶化!你,get到feel了嗎?
5.ac代碼
純c語言ac:
#include <stdio.h>
int maxn=1e5+5;
int maxk=1e5+5;
int main(){
int a[maxn];
int sum[maxn]; //前綴和求餘k結果,sum[i]表示(a[0]+a[1]+...+a[i])%k
int num[maxk];//記憶化,num[i]=x表示在當前已出現x個和爲i的前綴
int n,k,i;
scanf("%d%d",&n,&k);
for(i=0;i<n;i++) scanf("%d",&a[i]);
//初始化前綴和
sum[0]=a[0]%k;
for(i=1;i<n;i++) sum[i]=(a[i]+sum[i-1])%k;
//初始化記憶數組
num[0]=1; //初始狀態應該包含空前綴情況!,對後面的狀態遞推異常重要
for(i=1;i<maxk;i++) num[i]=0;
long long ans=0;
//線性同時記憶化
for(i=0;i<n;i++) ans+=num[sum[i]]++;
printf("%lld\n",ans);
return 0;
}