集合選數最值一類問題
一共有兩種類型,我分別介紹。
類型一
先來看一道簡單的題目:
POJ2442 Sequence
給你\(m\)個序列,每個序列有\(n\)個非負整數,你現在要在每個序列選一個數,這一共有\(n^m\)種方案,一種方案的值定義為所選的數的和,要你輸出值最小的\(n\)種方案的和。
數據範圍: \(0 \lt m \le 100\), \(0 \lt n \le 2000\)。
先考慮\(m = 2\)的情況,一共有\(n ^ 2\)種方案,設兩個序列為\(a, b\),假設我們已經把它們排好序了,即\(a_i \le a_{i+1} \ (1 \le i \lt n)\), \(b_i \le b_{i+1} \ (1 \le i \lt n)\)
至此,我們有了\(m = 2\)的合並算法,當\(m \gt 2\)
我們考慮與此題類似的一個問題,給定\(n\)個多重集合\(c_i\),第\(i\)個集合的大小為\(s_i\),要在每個集合中選一個數,一種方案的值定義為所選的數的和(或積),求第\(k\)大(或小)的方案的值。
對於此問題上面這個做法還不夠優秀。
以下介紹第一種更為優秀的做法。
以求第\(k\)大為例。
先把集合內元素排序,再以集合的最大值與次大值的差作為關鍵字對集合進行排序,即對於集合\(i\)滿足\(s_{i, j} \le s_{i, j + 1}, \ s_{i, 1} - s_{i, 2} \le s_{i + 1, 1} - s_{i + 1, 2}\)
把上面\(f\)的維數擴展到\(n\)維,\(f(p_1, p_2, \cdots , p_n)\)代表一個方案,第\(i\)個集合選了第\(p_i\)大的數。每個集合默認選擇最大的數,這種選擇作為初始方案,再進行逐個集合更改所選的數。我們只需要記錄當前待選擇的集合的編號\(i\),當前集合所選的數第\(j\)大的數,以及當前方案的值。之前的選擇\(p_1, p_2, \cdots p_{i - 1}\),完全可以丟棄,\(p_{i + 1}, p_{i + 2}, \cdots p_n\)默認都是選擇最大的,也不用記錄,所以\(f\)只有三維\((i, j, sum)\)。
對\(f(i, j, sum)\)的後繼進行定義:
若\(j \lt c_i\),\(f(i, j + 1, sum - s_{i, j} + s_{i, j + 1})\)是它的後繼;
若\(i \lt n\),\(f(i + 1, 2, sum - s_{i + 1, 1} + s_{i + 1, 2})\)是它的後繼;
若\(j = 2 \ \&\& \ i \lt n\),\(f(i + 1, 2, sum + s_{i, 1} - s_{i, 2} - s_{i + 1, 1} + s_{i + 1, 2})\)是它的後繼;
相應的,我們定義前驅。
我們把每種方案看成一個節點,它與它的後繼之間的邊為後繼邊,它與它的前驅之間的邊為前驅邊。
存在以下性質:
\(f(i, j, sum)\)不小於它的後繼,不大於它的前驅。
因為前面我們對集合進行了排序,
滿足\(s_{i, j} \ge s_{i, j + 1}\),所以第一種和第二種後繼(如果存在)存在這種性質。
滿足\(s_{i, 1} - s_{i, 2} \le s_{i + 1, 1} - s_{i + 1, 2}\),所以第三種後繼(如果存在)存在這種性質。
\(f(i, j, sum)\)可以有多個後繼,但只有唯一的前驅。
因為我們是逐位更改的,當前集合和所選的數的不同,這兩種方案通過後繼邊所能達到的方案不會有交。
對於任意合法的方案最終一定能通過前驅邊到達初始方案。
即從最初方案通過後繼邊可以到達任意合法方案。
這很顯然。
那麽這就是一棵樹。如果一種方案成為答案,那麽它的前驅肯定也是答案。那麽我們可以維護一個優先隊列,存當前可能成為答案的方案,第\(k\)個答案即為優先隊列第\(k\)次的最大值,一種方案出隊後把它的所有後繼加入優先隊列。
事實上,這種結構只要是一個\(DAG\),就可以用優先隊列維護了,只不過可能要去重。
這個算法的時間復雜度為\(O(nlog_{2}n + \sum_i^n{c_ilog_{2}c_i}+ klog_{2}mk)\),其中\(m\)為每種方案的平均後繼個數。由於\(m\)是常數,可以忽略,那麽時間復雜度就是\(O(nlog_{2}n + \sum_i^n{c_ilog_{2}c_i}+ klog_{2}k)\)。這個算法是相當優秀的。
模板題LibreOj #6254. 最優卡組
類型二
假設我們始終在一個集合內選數,即給你一個有\(n\)個元素的多重集合\(s\),選\(m\)個不同的數定義為一種方案,求第\(k\)大(或小)的方案的值。
利用上面的算法的思想,\(f(p_1, p_2, \cdots , p_m) \ (1 \le p_1 \lt p_2 \lt \cdots \lt p_m \le n)\)為一種方案。
定義後繼:如果\(f(p_1, p_2, \cdots , p_i + 1, \cdots , p_m)\)合法,那麽它為\(f(p_1, p_2, \cdots , p_m)\)的後繼。
類似的定義前驅。
但是這是一個DAG,如果這樣直接用優先隊列做,需要去重。
我們再次利用逐位更改的思想,多加一維\(i\),表示\(p_{i + 1}, \cdots , p_m\)不會更改。
\(f(i, p_1, p_2, \cdots , p_i, \cdots , p_m)\)的後繼有:
如果\(f(i, p_1, p_2, \cdots , p_i + 1, \cdots , p_m)\)合法,那麽它為\(f(i, p_1, p_2, \cdots , p_m)\)的後繼。
如果\(f(j, p_1, p_2, \cdots , p_j + 1, \cdots , p_m) \ (1 \le j \lt i)\),那麽它為\(f(i, p_1, p_2, \cdots , p_m)\)的後繼。
顯然它也有上面的三個性質。
那麽這就是一顆樹了,可以直接用優先隊列做,不需要去重了。
Sgu 421. k-th Product
給出\(n\)個整數\(a_1, a_2, \cdots , a_n\),問從中選\(m\)個數乘積第\(k\)大是多少。
數據範圍:\(1 \le n,k \le 10000\), \(1 \le m \le13\), \(k \le C^n_m\), \(-10^6 \le a_i \le10^6\)。
假如求的是數和的第\(k\)大,或正整數的乘積第\(k\)大是多少,那麽可以直接用上面的那個算法。
但是所選的負數的個數或影響乘積的正負,所以我們枚舉\(m\)中選的負數的個數。如果負數個數為偶數,這類方案的乘積非負,選擇求絕對值前\(k\)大的乘積。反之,這類方案的乘積非正,選擇求絕對值前\(k\)小的乘積。
參考資料
POJ2442 Sequence
第十三屆北航程序設計競賽預賽
第十三屆北航程序設計競賽預賽題解
LibreOj #6254. 最優卡組
Sgu 421. k-th Product
bzoj 1425: SGU 421 k-th Product
Sengxian‘s Blog
ZJOI2010講課
集合選數最值一類問題