1. 程式人生 > 其它 >AC1230 k倍區間(藍橋杯)

AC1230 k倍區間(藍橋杯)

技術標籤:題目筆記

題意:問有多少段的和能被k整除


這個題跟我入隊考試的題非常的像。

首先他問我們某一段的和是不是一個k的倍數,那麼一段和我們很自然的就聯想到字首和了。而且我們知道,如果對每一個字首和都進行%k處理,那麼就一定會使得某些數是0(假設這個數的位置是i),那麼就說明從1到i的所有的數的和都是k的倍數。那麼我們也會得到有的和並不是0,而是1~(k - 1)中的數。但是不妨礙裡面有相同的數。

假設 s[i] % k = x,那麼我們有取餘的定義可知:
s [ i ] / k = n ⋯ x k ∗ n + x = s [ i ] s[i] / k = n \cdots x \\ k * n + x = s[i]

s[i]/k=nxkn+x=s[i]
那麼假設還有一個數s[j]的取餘結果也是x,同理
k ∗ m + x = s [ j ] k * m + x = s[j] km+x=s[j]
兩式相減得:
k ∗ n − k ∗ m = s [ i ] − s [ j ] k ∗ ( n − m ) = s [ i ] − s [ j ] k*n - k*m=s[i]-s[j]\\ k*(n-m) = s[i]-s[j] knkm=s[i]s[j]k(nm)=s[i]s[j]
那麼就可以得到:
( s [ i ] − s [ j ] ) / k = ( n − m ) ⋯ 0 (s[i]-s[j]) / k = (n - m) \cdots 0
(s[i]s[j])/k=(nm)0

即:
( s [ i ] − s [ j ] ) % k = 0 (s[i]-s[j]) \% k = 0 (s[i]s[j])%k=0
那麼我們就能得到一下結論:

如果某個餘數在出現過之後在它後面再次出現過,那麼 [ i , j ]這個區間就可以是個k倍區間。

所以說,假設一個餘數出現過 m 次,那麼同時包含這個餘數的k倍區間的個數就是 (m - 1) * m / 2次(因為出現的第一次無法與前面組成k倍區間)

假設出現了5次

1 2 3 4 5

1 2

1 3

2 3

1 4

2 4

3 4

1 5

2 5

3 5

4 5

每個位置與前面的組成答案,就是 1 + 2 + 3 + 4 + …(n - 2) * (n -1) = (n - 1) * n / 2

當0為餘數的時候,就是它出現的次數就是(m + 1) * m / 2(餘數是0 的時候,第一次出現的那個位置也可以組成一個k倍區間)

#include <iostream>
#include <vector>
#define ll long long

using namespace std;

int n;
ll a[100000 + 10];
int k;
ll s[100000 + 10]; //看資料範圍我們知道,a[i]最大100000,你們到最後s[i]最大到100000 * 100000 = 10^10超出int
vector<ll> wei[100000 + 10]; //存第幾位出現的,其實這裡只存出現的個數就行;讀者可以自行更改一下

int main(){
    cin >> n >> k;
    for (int i = 1; i <= n; i++){
        cin >> a[i];
        s[i] = (s[i - 1] % k + a[i] % k) % k;
        wei[s[i]].push_back(i);
    }

    ll x = wei[0].size();
    ll cnt = x * (x + 1) / 2;

    for (int i = 1; i <= k; i++){
        x = wei[i].size();
        cnt += ((x - 1) * x / 2);
    }

    cout << cnt;
    return 0;
}

其實這種做法跟y總的思路差不多,