1. 程式人生 > >2017藍橋杯:k倍區間(字首和)

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;
}