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

🔗 CF2053D Refined Product Optimality

Problem Statement

題目簡述

給定兩個長度為 nn 的整數陣列 AABB。我們可以任意重排 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

約束條件

  • 1n,q21051 \le n, q \le 2 \cdot 10^5
  • n,q4105\sum n, \sum q \le 4 \cdot 10^5
  • 1ai,bi51081 \le a_i, b_i \le 5 \cdot 10^8

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

貪心:排序後配對最優

AABB 分別從小到大排序後,設排序後的陣列為 CCDD,則最大乘積為:

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),即 CiCjC_i \le C_jDi>DjD_i > D_j

a=Ci,b=Cja = C_i, b = C_j(故 aba \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)
情況一:cac \ge a

此時 d>cad > c \ge a,故 min(a,d)=a\min(a, d) = amin(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)。故 VpostVpreV_{post} \ge V_{pre}

情況二:c<ac < a

此時 c<abc < a \le b,故 min(b,c)=c\min(b, c) = cmin(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)

因為 aba \le b,所以 min(a,d)min(b,d)\min(a, d) \le \min(b, d)。故 VpostVpreV_{post} \ge V_{pre}

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

二分搜尋維護動態更新

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

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

  • idxidxCC 中值為 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(logn)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 是質數,根據費馬小定理:

a1aMOD2(modMOD)a^{-1} \equiv a^{\text{MOD}-2} \pmod{\text{MOD}}

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

這樣每次查詢只需 O(logn)O(\log n)(二分搜尋)+ O(logMOD)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(nlogn+qlogn)\mathcal{O}(n \log n + q \log n)
    • 初始排序 O(nlogn)O(n \log n)
    • 每次查詢:二分搜尋 O(logn)O(\log n),乘法反元素 O(logMOD)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

這裡什麼都沒有。