2017藍橋杯:k倍區間(字首和)
標題: 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
請嚴格按要求輸出,不要畫蛇添足地列印類似:“請您輸入…” 的多餘內容。
注意:
main函式需要返回0;
只使用ANSI C/ANSI C++ 標準;
不要呼叫依賴於編譯環境或作業系統的特殊函式。
所有依賴的函式必須明確地在原始檔中 #include
不能通過工程設定而省略常用標頭檔案。
提交程式時,注意選擇所期望的語言型別和編譯器型別。
分析;
不知道為什麼是最後一題 我的時間複雜度可能太高了
#include <iostream>
using namespace std;
int main()
{
int n,k;
int a[100010];
int sum;
cin >> n >> k;
for(int i = 0; i < n; i++) {
cin >> a[i];
}
int count = 0;
for(int i = 0; i < n; i++) {
sum = 0;
for(int j = i; j < n; j++) {
sum += a[j];
if(sum%k == 0){
count ++;
}
}
}
cout <<count;
return 0;
}
改後:
通過字首和的方法,把O(n方)的複雜度降低到了O(n),雖然學了挺久,看了挺久,但也算是學會了
主要思想:是使要加和的資料完全儲存,並且在下一次計算中直接呼叫,線性遞推.
分析:
統計字首和
sum[1] = a1;
sum[2] = a1+a2;
sum[i] = a1+a2+…+ai;
對於任意一段區間[l,r]的和就是sum[r]-sum[l-1].
(sum[r]-sum[l-1])%k 保證了[l,r]這段區間要麼%k等於0 要麼比k小 等於0這表示了正好是k的倍數 然後通過字首和相同的資料來判斷出剩下的k的倍數:(sum[r]-sum[l-1])%k == 0.變形後就是:sum[r]%k==sum[l-1]%k .
程式分析樣例:
輸入: 1 2 3 4 5
%k後字首和: 1 1 0 0 1
當i = 0, sum=0,bk[1]=1;
當i = 1, sum=1,bk[1]=2; //因為當bk[1]之前為1時 可得相減=0為k的倍數
當i = 2, sum=1,bk[0]=1;
當i = 3, sum=2,bk[0]=2; //同上理,當0-0時還是0
當i = 4, sum=4,bk[1]=3; //之前bk[1]有2個 所以有2種-法 所以sum加上2
最後統計bk[0]有幾個 sum+=bk[0] //因為之前只考慮了相減的情況 沒有考慮到本身
sum = 6;
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll bk[100010] = {0};
ll arr[1000010];
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int i = 0; i < n; i++)
scanf("%lld",&arr[i]);
arr[0] %= k;
ll sum = 0;
for(int i = 1; i < n; i++)
arr[i] = (arr[i]+arr[i-1])%k;
for(int i = 0; i < n; i++)
sum += (bk[ arr[i] ]++);
printf("%lld",sum+bk[0]);
return 0;
}