題目的難度顏色使用 Luogu 上的分級,由簡單到困難分別為 🔴🟠🟡🟢🔵🟣⚫。
rating: 550
Problem Statement
題意簡述
給定兩個長度分別為 N 與 M 的正整數序列 A=(A1,…,AN) 與 B=(B1,…,BM)。
請計算 i=1∑Nj=1∑M∣Ai−Bj∣ 的值,並對 998244353 取模。
Constraints
約束條件
- 1≤N,M≤3×105
- 1≤Ai,Bj<998244353
思路:排序後二分搜尋 + 前綴和優化
這道題的核心在於如何快速計算大量的「絕對值差」。
1. 暴力法的瓶頸
最直覺的做法是寫兩層迴圈,遍歷所有 (i,j) 對並計算 ∣Ai−Bj∣。
然而,由於 N,M 皆可達 3×105,總運算次數會達到 9×1010,這顯然會超時。我們需要一個接近 O(NlogN) 或 O(N+M) 的解法。
2. 拆解絕對值
絕對值 ∣x−y∣ 的定義取決於 x 和 y 的大小關係:
∣x−y∣={x−yy−xif x≥yif x<y
為了消去絕對值符號,我們必須知道 Ai 和 Bj 誰大誰小。
3. 固定一邊,對另一邊排序
我們可以固定序列 A 中的每一個元素 Ai,然後一次性計算它對所有 Bj 的貢獻。
為了快速區分哪些 Bj 比 Ai 小、哪些比 Ai 大,我們可以先將序列 B 進行排序。
假設 B 已經由小到大排好序。對於當前的 Ai,我們可以利用 二分搜尋 (Binary Search) 找到一個分界點 idx,使得:
- B1,B2,…,Bidx 皆小於等於 Ai
- Bidx+1,…,BM 皆大於 Ai
4. 推導公式
對於固定的 Ai,總和可以拆成兩部分:
- 比 Ai 小的部分 (B1…Bidx):
j=1∑idx∣Ai−Bj∣=j=1∑idx(Ai−Bj)=idx×Ai−j=1∑idxBj
- 比 Ai 大的部分 (Bidx+1…BM):
j=idx+1∑M∣Ai−Bj∣=j=idx+1∑M(Bj−Ai)=j=idx+1∑MBj−(M−idx)×Ai
5. 前綴和加速計算
上述公式中,∑Bj 的部分如果是每次重新加總,複雜度依然太高。
我們可以預先計算 B 的 前綴和 (Prefix Sum) 陣列 sb,其中 sb[k] 代表 ∑j=1kBj。
如此一來:
- ∑j=1idxBj 就是
sb[idx]。
- ∑j=idx+1MBj 就是
sb[M] - sb[idx]。
這兩個操作都可以在 O(1) 時間內完成。
題目要求對 998244353 取模。雖然 Python 自動處理大整數,但在計算過程中隨時取模可以保持數值較小。
另外,利用前綴和相減計算區間和時,若在其他語言中可能會出現負數,需注意 (a - b + MOD) % MOD 的處理。
複雜度分析
- 時間複雜度:O((N+M)logM)
- 對 B 排序:O(MlogM)。
- 計算 B 的前綴和:O(M)。
- 遍歷 A 並對每個元素進行二分搜尋:O(NlogM)。
- 空間複雜度: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