題目的難度顏色使用 Luogu 上的分級,由簡單到困難分別為 🔴🟠🟡🟢🔵🟣⚫。

🔗 ABC437D Sum of Differences

rating: 550

Problem Statement

題意簡述

給定兩個長度分別為 NNMM 的正整數序列 A=(A1,,AN)A = (A_1, \dots, A_N)B=(B1,,BM)B = (B_1, \dots, B_M)
請計算 i=1Nj=1MAiBj\displaystyle \sum_{i=1}^{N} \sum_{j=1}^{M} |A_i - B_j| 的值,並對 998244353998244353 取模。

Constraints

約束條件

  • 1N,M3×1051 \leq N, M \leq 3 \times 10^5
  • 1Ai,Bj<9982443531 \leq A_i, B_j < 998244353

思路:排序後二分搜尋 + 前綴和優化

這道題的核心在於如何快速計算大量的「絕對值差」。

1. 暴力法的瓶頸

最直覺的做法是寫兩層迴圈,遍歷所有 (i,j)(i, j) 對並計算 AiBj|A_i - B_j|
然而,由於 N,MN, M 皆可達 3×1053 \times 10^5,總運算次數會達到 9×10109 \times 10^{10},這顯然會超時。我們需要一個接近 O(NlogN)\mathcal{O}(N \log N)O(N+M)\mathcal{O}(N+M) 的解法。

2. 拆解絕對值

絕對值 xy|x - y| 的定義取決於 xxyy 的大小關係:

xy={xyif xyyxif x<y|x - y| = \begin{cases} x - y & \text{if } x \ge y \\ y - x & \text{if } x < y \end{cases}

為了消去絕對值符號,我們必須知道 AiA_iBjB_j 誰大誰小。

3. 固定一邊,對另一邊排序

我們可以固定序列 AA 中的每一個元素 AiA_i,然後一次性計算它對所有 BjB_j 的貢獻。
為了快速區分哪些 BjB_jAiA_i 小、哪些比 AiA_i 大,我們可以先將序列 BB 進行排序

假設 BB 已經由小到大排好序。對於當前的 AiA_i,我們可以利用 二分搜尋 (Binary Search) 找到一個分界點 idx,使得:

  • B1,B2,,BidxB_1, B_2, \dots, B_{idx} 皆小於等於 AiA_i
  • Bidx+1,,BMB_{idx+1}, \dots, B_M 皆大於 AiA_i

4. 推導公式

對於固定的 AiA_i,總和可以拆成兩部分:

  1. AiA_i 小的部分 (B1BidxB_1 \dots B_{idx})

    j=1idxAiBj=j=1idx(AiBj)=idx×Aij=1idxBj\sum_{j=1}^{idx} |A_i - B_j| = \sum_{j=1}^{idx} (A_i - B_j) = \text{idx} \times A_i - \sum_{j=1}^{idx} B_j

  2. AiA_i 大的部分 (Bidx+1BMB_{idx+1} \dots B_M)

    j=idx+1MAiBj=j=idx+1M(BjAi)=j=idx+1MBj(Midx)×Ai\sum_{j=idx+1}^{M} |A_i - B_j| = \sum_{j=idx+1}^{M} (B_j - A_i) = \sum_{j=idx+1}^{M} B_j - (M - \text{idx}) \times A_i

5. 前綴和加速計算

上述公式中,Bj\sum B_j 的部分如果是每次重新加總,複雜度依然太高。
我們可以預先計算 BB前綴和 (Prefix Sum) 陣列 sb,其中 sb[k] 代表 j=1kBj\sum_{j=1}^k B_j
如此一來:

  • j=1idxBj\sum_{j=1}^{idx} B_j 就是 sb[idx]
  • j=idx+1MBj\sum_{j=idx+1}^{M} B_j 就是 sb[M] - sb[idx]

這兩個操作都可以在 O(1)\mathcal{O}(1) 時間內完成。

模運算注意

題目要求對 998244353998244353 取模。雖然 Python 自動處理大整數,但在計算過程中隨時取模可以保持數值較小。
另外,利用前綴和相減計算區間和時,若在其他語言中可能會出現負數,需注意 (a - b + MOD) % MOD 的處理。

複雜度分析

  • 時間複雜度O((N+M)logM)\mathcal{O}((N+M) \log M)
    • BB 排序:O(MlogM)\mathcal{O}(M \log M)
    • 計算 BB 的前綴和:O(M)\mathcal{O}(M)
    • 遍歷 AA 並對每個元素進行二分搜尋:O(NlogM)\mathcal{O}(N \log M)
  • 空間複雜度O(N+M)\mathcal{O}(N+M),用於儲存陣列與前綴和。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from bisect import bisect_right
from itertools import accumulate

MOD = 998244353

def solve():
n, m = map(int, input().split())
A = list(map(int, input().split()))
B = list(map(int, input().split()))
assert len(A) == n and len(B) == m

B.sort()
sb = list(accumulate(B, initial=0))

ans = 0
for x in A:
idx = bisect_right(B, x)
ans += idx * x - sb[idx]
ans += sb[m] - sb[idx] - (m - idx) * x
ans %= MOD
print(ans)

if __name__ == "__main__":
solve()

寫在最後

PROMPT

這裡什麼都沒有。