CF895B XK Segments 題解 二分

quanjun發表於2024-08-13

題目連結:https://codeforces.com/problemset/problem/895/B

題目大意

給你一個長度為 \(n\) 的數列 \(a_1, a_2, \ldots, a_n\)。求數列中存在多少個不同的下標對 \((i, j)\) 滿足如下條件:

\(a_i \le a_j\) 並且恰好有 \(k\) 個整數 \(y\) 滿足 \(a_i \le y \le a_j\)\(y\) 能被 \(x\) 整除。

注:在本題中,若 \(i \neq j\),則下標對 \((i, j)\) 和下標對 \((j, i)\) 是不同的下標對。

解題思路

首先我們可以將 \(a_1 \sim a_n\) 從小到大排序。

很明顯排序前後的下標對數量不會變化。

但是排序後和下標 \(i\) 構成滿足條件的下標對 \((i, j)\) 的下標 \(j\) 將會在一個連續的範圍內。

其次要注意,本題中資料處理的過程中可能會發生超出 int 範圍的情況,所以乾脆全部都開 long long。即:

#define int long long

然後我們就可以列舉每個下標 \(i\),判斷 \(a_i\) 開始(即下標 \(i, i+1, i+2, \ldots, n\) 範圍內)存在多少個下標 \(j\) 滿足

\(a_i \le a_j\) 並且恰好有 \(k\) 個整數 \(y\) 滿足 \(a_i \le y \le a_j\)\(y\) 能被 \(x\) 整除。

這個條件,然後把數量加起來就可以了。

具體來說:

  • \(i = 1\) 時,判斷 \([1, n]\) 範圍記憶體在多少下標 \(j\)\(i\) 構成的下標對 \((i, j)\) 滿足上述條件;
  • \(i = 2\) 時,判斷 \([1, n]\) 範圍記憶體在多少下標 \(j\)\(i\) 構成的下標對 \((i, j)\) 滿足上述條件;
  • ……
  • \(i = n\) 時,判斷 \([1, n]\) 範圍記憶體在多少下標 \(j\)\(i\) 構成的下標對 \((i, j)\) 滿足上述條件。

即對於每個下標 \(i\) 判斷區間 \([1, n]\) 範圍記憶體在多少個下標 \(j\) 滿足條件。

\(a_i = a_j\) 時,\(j\) 甚至可能小於 \(i\)

(注意 \(i\) 可以等於 \(j\),即下標對 \((i, i)\) 也可能是滿足條件的)

而且我們會發現,對於每個下標 \(i\),滿足條件的下標 \(j\) 是一個連續的區間,這很好理解:

設滿足條件的 \(j\) 所在的區間為 \([l_i, r_i]\)(而一整個區間是 \([1, n]\)),則:

  • 區間 \([1, l_i - 1]\) 範圍內:\(x\) 的倍數 \(\lt k\) 個;
  • 區間 \([l_i, r_i]\) 範圍內:\(x\) 的倍數恰好 \(k\) 個;
  • 區間 \([r_i + 1, n]\) 範圍內:\(x\) 的倍數 \(\gt k\) 個。

所以我們現在要做的事情就是找 \(i\) 對應的最小值 \(l_i\) 和最大值 \(r_i\)

有一種特殊情況,\(k = 0\)

\(k = 0\)

此時若 \(a_i\)\(x\) 的倍數,則無解。(因為此時至少有一個 \(a_i\)\(x\) 的倍數)

\(a_i\) 不是 \(x\) 的倍數,則 \(l_i = a_i\)\(r_i = \lceil \frac{a_i}{x} \rceil \times x - 1\)

\(k \gt 0\)

\(l_i = \lceil \frac{a_i}{x} \rceil \times x + (k - 1) \times x\)

\(r_i = l_i + x - 1\)

確定好 \(l_i\)\(r_i\) 之後 —— 我們用 l 表示 \(l_i\),用 r 表示 \(r_i\),那麼

lower_bound(a+i, a+n+1, l) - a

對應的就是下標 \(l_i\)

upper_bound(a+i, a+n+1, r) - a - 1

對應的就是下標 \(r_i\)

\([l_i, r_i]\) 範圍內滿足條件的下標一共有 \(r_i - l_i + 1\) 即:

upper_bound(a+i, a+n+1, r) - lower_bound(a+i, a+n+1, l)

個。

對於每個下標 \(i\),確定好 \(l_i\)\(r_i\) 之後,對上式進行累加即可。

示例程式

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
#define int long long
int n, x, k, a[maxn], ans;

signed main() {
    cin >> n >> x >> k;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a+1, a+n+1);
    for (int i = 1; i <= n; i++) {
        int l, r;
        if (k == 0) {
            if (a[i] % x == 0)
                continue;
            l = a[i], r = (a[i] + x - 1) / x * x - 1;
        }
        else {
            l = (a[i] + x - 1) / x * x + (k - 1) * x;
            r = l + x - 1;
        }
        ans += upper_bound(a+1, a+n+1, r) - lower_bound(a+1, a+n+1, l);
    }
    cout << ans << endl;
    return 0;
}

相關文章