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

🔗 🟢 CF2053D. Refined Product Optimality

Problem Statement

題目簡述

給定兩個長度為 nn 的整數陣列 AA 和 BB。我們可以任意重排 BB。
你的目標是最大化 P=∏i=1nmin⁡(Ai,Bi)P = \prod\limits_{i=1}^n \min(A_i, B_i)。
題目共有 qq 次修改操作,每次操作會給定 (o,x)(o, x),若 o=1o=1 則將 AxA_x 加 1,若 o=2o=2 則將 BxB_x 加 1。
請輸出初始狀態以及每次修改後,可以得到的最大 PP 值(對 998244353998244353 取模)。

Constraints

約束條件

  • 1≤n,q≤2⋅1051 \le n, q \le 2 \cdot 10^5
  • ∑n,∑q≤4⋅105\sum n, \sum q \le 4 \cdot 10^5
  • 1≤ai,bi≤5⋅1081 \le a_i, b_i \le 5 \cdot 10^8

思路:貪心 + 排序 + 二分查找

貪心:排序後配對最優

將 AA 和 BB 分別從小到大排序後,設排序後的陣列為 CC 和 DD,則最大乘積為:

P=∏i=1nmin⁡(Ci,Di)P = \prod_{i=1}^n \min(C_i, D_i)

為什麼排序後配對最優?

假設 A,BA, B 排序後分別為 C,DC, D。若存在逆序對 (i,j)(i, j),即 Ci≤CjC_i \le C_j 但 Di>DjD_i > D_j。

令 a=Ci,b=Cja = C_i, b = C_j(故 a≤ba \le b),d=Di,c=Djd = D_i, c = D_j(故 c<dc < d)。比較兩種配對:

  • 交換前(逆序):Vpre=min⁡(a,d)×min⁡(b,c)V_{pre} = \min(a, d) \times \min(b, c)
  • 交換後(同序):Vpost=min⁡(a,c)×min⁡(b,d)V_{post} = \min(a, c) \times \min(b, d)
情況一:c≥ac \ge a

此時 d>c≥ad > c \ge a,故 min⁡(a,d)=a\min(a, d) = a,min⁡(a,c)=a\min(a, c) = a。

  • Vpre=a×min⁡(b,c)V_{pre} = a \times \min(b, c)
  • Vpost=a×min⁡(b,d)V_{post} = a \times \min(b, d)

因為 c<dc < d,所以 min⁡(b,c)≤min⁡(b,d)\min(b, c) \le \min(b, d)。故 Vpost≥VpreV_{post} \ge V_{pre}。

情況二:c<ac < a

此時 c<a≤bc < a \le b,故 min⁡(b,c)=c\min(b, c) = c,min⁡(a,c)=c\min(a, c) = c。

  • Vpre=min⁡(a,d)×cV_{pre} = \min(a, d) \times c
  • Vpost=c×min⁡(b,d)V_{post} = c \times \min(b, d)

因為 a≤ba \le b,所以 min⁡(a,d)≤min⁡(b,d)\min(a, d) \le \min(b, d)。故 Vpost≥VpreV_{post} \ge V_{pre}。

綜上,將逆序對交換成同序後,乘積不會變小。反覆交換直到兩陣列同序即得最優解。

二分搜尋維護動態更新

由於題目涉及 qq 次單點增加(每次 +1+1),我們不能每次重新排序(O(nlog⁡n)O(n \log n) 太慢)。需要在 O(log⁡n)O(\log n) 時間內維護排序陣列和答案。

當原陣列 AiA_i 的值 vv 增加 1 時,相當於將排序陣列 CC 中任意一個值為 vv 的位置變成 v+1v+1。為了保持 CC 的有序性,我們選擇 CC 中最右邊的那個 vv 進行增加:

  • 設 idxidx 為 CC 中值為 vv 的最後一個元素位置
  • 將 C[idx]C[idx] 從 vv 更新為 v+1v+1
  • 由於 C[idx+1]≥v+1C[idx+1] \ge v+1(若存在),更新後 C[idx]≤C[idx+1]C[idx] \le C[idx+1],陣列依然有序

由於 CC 是有序的,使用二分搜尋即可在 O(log⁡n)O(\log n) 內找到該位置。

利用模意義下的乘法反元素(逆元)更新乘積

我們維護當前的總乘積 PP。更新 C[idx]C[idx] 時,只有當它是瓶頸時,乘積才會改變:

  • 若 C[idx]<D[idx]C[idx] < D[idx]:該位置對乘積的貢獻從 C[idx]C[idx] 變為 C[idx]+1C[idx] + 1

P′=P×(C[idx]+1)C[idx]P' = P \times \dfrac{(C[idx]+1)}{C[idx]}

  • 若 C[idx]≥D[idx]C[idx] \ge D[idx]:該位置貢獻為 D[idx]D[idx],更新 C[idx]C[idx] 不影響最小值,PP 不變
模意義下的乘法反元素(逆元)計算

由於 MOD=998244353\text{MOD} = 998244353 是質數,根據費馬小定理:

a−1≡aMOD−2(modMOD)a^{-1} \equiv a^{\text{MOD}-2} \pmod{\text{MOD}}

在 Python 中可以直接用 pow(B[x], -1, MOD) 計算。

這樣每次查詢只需 O(log⁡n)O(\log n)(二分搜尋)+ O(log⁡MOD)O(\log \text{MOD})(快速冪求乘法反元素)。

實現細節

  • 維護兩個排序陣列 sl1(對應 CC)和 sl2(對應 DD)
  • 同時保留原陣列 A,BA, B 以得知每次操作增加的具體數值
  • 操作 AxA_x 時:取 v=A[x]v = A[x],找 bisect_right(sl1, v) - 1 作為更新點,判斷是否需更新 prod,然後將 sl1[idx] 和 A[x] 都加 1

複雜度分析

  • 時間複雜度:O(nlog⁡n+qlog⁡n)\mathcal{O}(n \log n + q \log n)
    • 初始排序 O(nlog⁡n)O(n \log n)
    • 每次查詢:二分搜尋 O(log⁡n)O(\log n),乘法反元素 O(log⁡MOD)O(\log \text{MOD})
  • 空間複雜度:O(n)\mathcal{O}(n),用於存儲排序陣列

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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from bisect import bisect_right

MOD = 998244353

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

sl1 = sorted(A)
sl2 = sorted(B)

prod = 1
for x, y in zip(sl1, sl2):
prod = (prod * min(x, y)) % MOD
ans = [prod]
for _ in range(q):
o, x = map(int, input().split())
x -= 1
if o == 1:
idx = bisect_right(sl1, A[x]) - 1
if sl1[idx] < sl2[idx]:
prod = (prod * pow(A[x], MOD - 2, MOD)) % MOD
prod = (prod * (A[x] + 1)) % MOD
sl1[idx] += 1
A[x] += 1
else:
idx = bisect_right(sl2, B[x]) - 1
if sl2[idx] < sl1[idx]:
prod = (prod * pow(B[x], -1, MOD)) % MOD
prod = (prod * (B[x] + 1)) % MOD
sl2[idx] += 1
B[x] += 1
ans.append(prod)
print(*ans)

if __name__ == "__main__":
t = int(input())
for _ in range(t):
solve()

寫在最後

PROMPT

這裡什麼都沒有。