110 學年度全國資訊學科能力競賽各題詳解

筆者這次也參與了全國賽的驗題,這邊放一份另一位驗題者與筆者提供的詳解。題本的連結在這裡,pC / pE / pH / pI 的時間限制分別為 2.5s / 2.5s / 2.0s / 0.1s,其餘題目皆為 1.0s。另,讀者也能在這裡找到與下文基本相同的內容。

A. 髮廊服務優化問題 (barbershop)

n 個人一起來店,第 i 個人的服務時間為 ti。每個客人等待時間是前面人 + 自己服務時間總和,求等待時間總和的最小值。例如:三個人服務時間為 2,1,3,若服務順序為 1,2,3 則等待時間總和為 2+(2+1)+(2+1+3)=11

等價題序:給一個陣列 t:={ti}ni=1,將 t 重新排列使得 nt1+(n1)t2+(n2)t3++tn 最小。由上式不難得知,順序越前面被乘的係數就越大,所以可以貪心地將數字從小排到大。複雜度 O(nlogn)

B. 校園公車 (bus)

給定由 n 條線段組成的折線 P:={¯PiPi+1}ni=1 與一點 O,問 OP 的最短距離。

先考慮這個問題:給定一線段 ¯PQ 與一點 O,求 O¯PQ 的距離。首先我們作 O¯PQ 的垂足 H,並試著計算 ¯OH。注意 ¯OH 其實就是 ΔOPQ¯PQ 為底時的高,所以可以用下面的方法計算:

Length_OH(O: Point, PQ: Segment):
    A ← ΔOPQ
    return 2A/|PQ|

ΔOPQ 可以用 Shoelace formula 計算。

但事情沒有這麼簡單。O¯PQ 的距離並不總是 ¯OH:當 OPQOQP 為鈍角時,H 並不在 ¯PQ 上,而此時的最短距離是 ¯OP¯OQ。由畢氏定理可知,OPQ 為鈍角的充要條件是 ¯OP2+¯PQ2<¯OQ2,因此 O¯PQ 間的距離就可以用下面的方法計算:

Distance(O: Point, PQ: Segment):
    if |OP|^2 + |PQ|^2 < |OQ|^2 then
        return |OP|
    else if |OQ|^2 + |PQ|^2 < |OP|^2 then
        return |OQ|
    else
        return Length_OH(O, PQ)
    end if

代入 ¯PQ=¯P1P2,¯P2P3,,¯PnPn+1 並取最小值就是答案。

C. 街頭藝人 (busker)

給一張 n 點(城市) m 邊的有向圖 G0。我們對 G0 的每條邊都加上 k 個點(村莊),得到一張 n+mk 節點的有向圖 G,並賦予點權重 c:V(G)Z(每個節點的收支)。設 CG 上的一個簡單環且 uV(C)。若從 u 出發沿著 C 走一圈,任意前綴點權重和(所持金)都 0,我們就說 CG 的一個好環,而 uC 的一個好起點。請找出 G 的任一個好環 CC 的任一個好起點 u,並求出 C 上有幾個點可以當作好起點,這些好起點又有幾個在 G0 上。

非負環必可以找到好起點

顯然一個好環有點權重和 0。我們證明若一個環的點權重和 0,則它必定是一個好環(i.e. 可以找到一個好起點)。

C 是一個點權重和 0 的環。隨意找一個 u1V(C),並設從 u1 沿著 C 出發走一圈經過的點依序是 u2,u3,,u|V(C)|。對於所有的 i{0,1,,|V(C)|},考慮在每個節點的所持金數列 s(也就是環的點權前綴和):

s(i)={0,if i=0, c(u1)+c(u2)++c(ui),if i1.

考慮路途任一所持金最小的時刻 k{0,1,,|V(C)|1},也就是 k 滿足

s(k)=min0i|V(C)|1s(i).

注意我們有 s(|V(C)|)0=s(0),因此

s(k)=min0i|V(C)|s(i).

接著證明 uk+1 是一個好起點。

對於所有的 i{k+1,k+2,,|V(C)|},從 uk+1 出發沿著 C 走到 ui 的點權重和是 s(i)s(k),但 s(k)s(i),故 s(i)s(k)0

對於 i{1,2,,k},從 uk+1 出發沿著 C 走到 ui 的點權重和是 s(|V(C)|)+s(i)s(k)。但 s(|V(C)|)0,依然有 s(|V(C)|)+s(i)s(k)s(i)s(k)0

非負環求好起點的數量

到前面為止,已經證明了任意一個非負環必定存在一個好起點(也就是題目所求的入能敷出演藝路線)。這裡假設讀者已經很熟悉 O(VE) 找負環的技巧。接著來處理題目的另一個詢問,也就是環上共有幾個好起點。

方法 1:DP

定義 s 的前綴最小值 αiu1 沿著 C 走到 ui 時的最小所持金:

αi=min0kis(i).

類似地,我們也可以定義 s 的後綴最小值 β

βi=minik|V(C)|s(i).

若改變起點從 ux 開始沿著 C 走一圈,可以推出所持金最小的時刻如下:

  • uxu|V(C)| 間:βxs(x1)
  • u1ux1 間:αx1+s(|V(C)|)s(x1)
  • 上面兩者取最小值即可求得以 ux 為起點繞 C 走一圈的最小所持金

由於環展開頂多只有 n+mk 個節點,故這邊複雜度為 O(n+mk)

方法 2:

不失一般性假定 k=0,亦即任意時間所持金皆非負,對於所有的 i 皆有 s(i)0。接著證明 ui 是好起點若且唯若 s(i1)s(j) for all j{i,i+1,,|V(C)|}

  1. ji,從 ui 沿著 C 走到 uj 的點權重和是 s(j)s(i1),故 是直接結論。
  2. j<i,從 ui 沿著 C 走到 uj 的點權重和變成 s(|V(C)|)+s(j)s(i1),又因為 s(j)0,我們有 s(|V(C)|)+s(j)s(i1)s(|V(C)|)s(i1)0, 這給出了 的證明。

因此只要找出了一個非負環,就能在 O(|C|)=O(n+mk) 時間求出所有的好起點。

Subtask 1: n20

直接建立 G,注意我們有 |V(G)|=n+mk=O(n3)|E(G)|=m(k+1)=O(n3)。對於每條邊 (u,v),轉移權重 c(u)。用 Bellman–Ford 演算法找出一個非負環,時間複雜度為 O(|V(G)||E(G)|)=O(n6)

Subtask 2: n90

在 Subtask 1 中,Bellman–Ford 的節點更新次數只要達到 n,就找到一個非負環了,故時間複雜度進一步降成 O(n|E(G)|)=O(n4)

Subtask 3: n2000,m8000

我們建圖的目的是找出好環,所以可以在 G0 上找出非負環 C0,復原成 C 後再找出好起點。設 (u,v)E(G0) 且在 G 上被加入的點權重分別是 a1,a2,,ak,則我們直接轉移權重 c(u)+ai(u,v) 上,如此一來 Bellman–Ford 的時間就降為 O(mn)

D. 汽車不再繞圈圈 (car)

給一張 nm 邊的有向圖,每條邊都可以反轉,但反轉第 i 條邊需要權限 ci,反轉複數條邊所需的權限值就是反轉邊的最大權限。問至少要有多少權限才可以將邊反轉使得整張圖沒有環?並輸出反轉方案。

Subtask 1: n,m20

可以枚舉要反轉邊的 subset,之後再檢查環有沒有被刪掉。

Subtask 2: ci100

答案 = 選的邊裡的最大權重,直接枚舉這個數字 x,代表可以將權限 x 以下的所有邊做任意反轉操作。至於要怎麼求出方案以及判斷 x 是否是可行解,這裡提供一個方法:

  1. 首先將 x 的所有邊刪除,做 >x 邊的拓樸排序。
  2. 由於 x 的邊可以任意改動方向,我們就改動邊的方向來符合上個步驟求出的拓樸順序。

在決定 x 以後可以花 O(n+m) 的時間檢查並求出可行解,複雜度是 O(c(n+m))

Subtask 3: n,m105,ci109

在 subtask 2 中我們已經知道了枚舉一個解的權限值判斷是否可行的方法。可以發現,若枚舉的解越大,可以改動的邊越多,也更有可能存在可行解,存在單調性。因此我們可以將 subtask 2 中枚舉權重的部分改成二分搜尋,在 O((n+m)logc) 的時間做完。當然也能先將邊權 c1,,cm 排序,再對排序後的 ci 們做二分搜,時間複雜度進一步降成 O((n+m)logm)

E. 天空競技場 (colosseum)

有座 n 層樓的競技場,其中第 i 層開設時間、金幣枚數門檻、挑戰費時、結束後獲得金幣枚數分別為 xi,yi,ti,wi。參賽者初始擁有的金幣枚數為 0,但可以從任一層開始挑戰。在抵達第 i 層時:

  • 若已擁有 yi 枚金幣且抵達時間 <x,可以選擇等待競技開始或直接進入第 i+1
  • 若已擁有 yi 枚金幣且抵達時間 x 就一定要花 ti 時間參加競技
  • 否則只能直接進入第 i+1

請問參賽者在時間 m 時能獲得的最大金幣數量是多少?本題 n3000001wi1000

Subtask 2: xi=yi=0

用兩個 pointer 來模擬 queue,找出在 ti 區間和不超過 m 的情況下,wi 區間和的最大值,時間複雜度是 O(n)

Subtask 3: xi=0

由於每一層只要達到門檻數量的金幣就會發生戰鬥,可以用一個 queue Q 來儲存金幣數 f 與時間 s,並依序考慮第 1 層到第 n 層。加入第 i 層時,先把 (0,0) 插入 Q 的後端,接著將 fyi(f,s) 們全部加上 (wi,ti),最後再從 Q 的前端踢掉 s>m 的那些 (f,s) 們。可以得到一個 O(n2) 時間複雜度的做法,再用 Fenwick tree 進一步加速到 O(nlogn)

Subtask 1: n1000

在金幣數相同的情況下,可以知道時間比較早的不會比較差。我們用一個 hash map M 來儲存金幣數 f (key type) 與時間 s (mapped type),並依序考慮第 1 層到第 n 層。加入第 i 層時,先加入 (0,0)M 中,接著對於 M 裡所有滿足 fyi(f,s) 數對,做下面這件事情:

  1. sxi,則把 (f,s) 踢掉,並根據 s+ti 是否超過 m 決定要不要加入 (f+wi,s+ti)M 中。
  2. 否則,根據 xi+ti 是否超過 m,決定要不要加入 (f+wi,xi+ti)M 中。

|M| 最大為 wi+1,總共做 n 次,時間複雜度為 O(nwi)

滿分做法(並沒有)

在驗題的過程中,我們誤以為時間越早金幣越多越好,但這是不正確的。考慮 m=10,x={0,0,3,0,0},y={0,0,10,110,0},t={1,1,1,5,5},w={100,10,1,1,1000}。最佳解是從第 2 層出發,第 3 層等待,最後得到的 1011 枚金幣(這時離開第 3 層的時間與金幣數分別為 411)。另一方面,若從第 1 層出發且第 3 層不等待,則離開第 3 層的時間與金幣數分別為 2110,看似比前者為優,但因過了 y4=110 的門檻被強迫參加第 4 層的競技,最後只能拿到 111 枚金幣。

基本上這種測試資料需要特別構造。由於比賽時的測資是隨機生成的,在我們的機器上平行跑了時間複雜度 O(nwi) 的解 12 小時後終於確認比賽時的輸入輸出都是對的,只是這個執行時間根本不可能過得了時限。

F. 挑水果 (fruit)

  • 一開始船上有 c 個種類的水果,第 i 種類有 ni
  • 依序經過 c 個城市,每經過一個城市可以決定要不要把船上所有前 i 種類的水果給當地盤商賣
  • 積載每顆水果經過都市 i 需要積載成本 pi
  • 把每顆水果給都市 i 的盤商賣需要成本 si
  • 在都市 i 賣種類 j 的水果最後只會賣出 ri,j

問若積載成本和銷售成本總和不超過 T 的前提下,最多能賣幾顆水果?限制:

  • 1c,ni40
  • 1pi,si1000
  • T107

Subtask 1: c20,T30000

直接枚舉所有可能的銷售都市集合,組合數為 Ω(2c)

Subtask 2: T30000

考慮 DP 狀態 dp[i][j]

  • i 是最後一次卸貨給盤商銷售的城市(= 賣出最大的水果種類)
  • j 是到城市 i 卸貨為止前 i 種類水果積載 + 銷售的費用和
  • dp[i][j] 為最後一次在城市 i 卸貨,前 i 種水果總花費 j 時最大賣出的水果數量
  • 特殊地,若 i=0 則代表尚未卸貨

枚舉 i 的上一個卸貨點 k0k<i):

  • 最後一次卸貨所卸的水果種類為 k+1i,令 N= 船上 k+1i 的水果數量和
  • 最後一次卸貨產生的銷售費用為 Nsi
  • 水果 k+1i 產生的積載費用為 N(p1+p2++pi)
  • 此次卸貨,在此城市將賣出 ri,k+1+ri,k+2++ri,i 顆水果

可以注意到上面的轉移式中,n,s,p,ri 的和都可以用前綴和來 O(1) 求出。因此只要輸入時先預處理這些數列的前綴和,便可以做到狀態 O(cT) 轉移 O(c) 的 DP,時間複雜度為 O(c2T)

滿分做法

因為 T 的範圍太大不適合拿來當狀態,只好試著想辦法用水果數量來構造對偶狀態。考慮 DP 狀態 dp[i][j]

  • i 是最後一次卸貨給盤商銷售的城市(= 賣出最大的水果種類)
  • j 是到城市 i 為止前 i 種類水果銷售數量和
  • dp[i][j] 為最後一次在城市 i 卸貨,前 i 種水果銷售量 j 時所花的最小成本
  • 特殊地,若 i=0 則代表尚未卸貨

轉移和上面 O(c2T) 的作法極為類似,分別列出每次卸貨所產生的積載、銷售成本以及銷售量轉式會很清楚因此就不多加贅述了。答案為滿足 dp[i][j]T 最大可能的 j

整體的複雜度分析:

  • 水果數量為 ni 個,狀態數 O(cni)
  • 轉移 O(c)
  • 因此 DP 的時間複雜度為 O(c2ni)

G. 鳳梨關稅 (pineapple)

給定一棵有根樹,一開始每條邊權重都是 1,處理下列兩種操作:

  1. 把某條邊的權重變 0
  2. 詢問根節點到某一節點的權重和。

詢問數和節點數都是 105 等級。

Subtask 2

Root 的度數為 1,且其他節點度數最多為 2 給的樹是一條 path。

這筆 subtask 可以用一棵 BIT 或線段樹來維護每條邊的權重,詢問等價於求序列前綴和。複雜度是 O(nlogn)

滿分做法

假設某筆詢問將邊 (x,y)1 改成 0,其中 x 是父節點。這個操作相當於將子樹 y 的答案都減 1。每次的操作都是一個子樹操作,因此我們可以先計算初始答案,並將樹的時間戳記用來做線段樹的 index,即可在每次操作 O(logn) 的時間維護訊息。這個技巧一般被稱作 Euler Tour Technique 或者俗稱樹壓平。對此技巧不熟悉的可以在上面連結或相關關鍵字找到資料。

H. 天竺鼠遊行 (puipui)

對於一個數列 a=a1,a2,,ak,定義 f(a)

max1ik|a(imodk)+1ai|.

上式和題目所求的環狀數列最大高度差等價。現在給定 n 個數 h1,h2,,hn,想要從裡面抓出 pk 項的數列 a1,a2,,ap(當然我們有 npk)。請問

max1ipf(ai)

的最小值是多少?

Subtask 2: k=n,p=1

不失一般性,假定 h1h2hn。經過亂嘗試以後我們發現似乎只要把奇數項從小排到大,再把偶數項從大排到小,就能給出最佳解。(至於為什麼亂試會得到這個結論,就留給讀者自行思考了 [窩不知道.jpg])

為了接下來的討論方便,以下給出一些定義:

  1. 一個 σSn1,2,,n 的排列,我們寫成 σ=σ(1),σ(2),,σ(n), 一個例子是 σ=1,3,5,4,2
  2. σ|i 為「從 σ 去掉所有 >i 的數後得到的 1,2,,i 排列」。例如當 σ=1,3,5,4,2 時,σ|3 就是 1,3,2
  3. 對於所有的 τSi,定義 g(τ;h)max1ji|hτ((jmodi)+1)hτ(j)|. 白話一點解釋,g(τ;h) 代表的是將前 i 隻老鼠以 τ 排成環狀數列所得的最大高度差。 在本 subtask 中,我們要找出 σSn 使得 g(σ;h) 有最小值。
  4. 定義 σ={1,3,,n2,n,n1,n3,,2,if n is odd,1,3,,n3,n1,n,n2,,2,if n is even.

我們用數學歸納法證明 σ 會給出最小的 g(σ;h)。對於 n3g(σ;h)=hnh1,在 n3 以下的情況確實為最佳解。若 n4 並考慮把 n 插入 σ|n1 以得到 σ 的過程。由於 n 被插入 n2n1 中間且 n1n3 相鄰,我們知道

g(σ|n1;h)hn1hn3hn1hn2.

另一方面,對於任意 σSn,我們本來就有

{g(σ;h)hnhn2,g(σ;h)g(σ|n1;h).

所以可以分兩種情況:

  1. g(σ|n1;h)<hnhn2g(σ;h)=hnhn2,故 σ 為最小的 g(σ;h)
  2. g(σ|n1;h)hnhn2g(σ;h)=g(σ|n1;h),根據歸納法假設知道 σ 也會最小化 g(σ;h)

只要先將 h 排序好,就能在 O(n) 時間內得出答案,時間複雜度為 O(nlogn)

Subtask 3: p=1

一樣先將 h 排序好,接著再用 sliding windowO(n) 時間內得出答案,時間複雜度與 subtask 2 相同。

滿分做法

直接對答案 x 做二分搜,判斷是否能切出 pk 項的數列 a 滿足 f(a)x。時間複雜度為 O(nlogn+nlogH),其中 Hhi 的最大值。

I. 鐵路鋪設 (rail)

給定 L,問 2×L 方格能放幾個不相交的迴圈使得所有方格中心都被經過,且每個迴圈最多只能有 1 條斜邊。

DiL=i 時的方法數。對於任何一張合法的圖,我們從左邊到右邊找出第一個不會切到迴圈的鉛直線:

可以發現若鉛直線切出的寬度是 lil,則被切出 l 的那邊有 2l3 種填滿的方式,這樣一來我們就有

Di=i2j=0(2(ij)3)Dj.

Subtask 1: L7

直接帶入遞迴式計算,時間複雜度是指數級。

Subtask 2: L1000

根據前面所介紹的遞迴式依序計算 D0,D1,,DL,並用一個 DP 表格記錄算過的 Di 們,需時 O(L2)

Subtask 3: L105

整理遞迴式

Di=i2j=0(2(ij)3)Dj=i2j=0(2i2j3)Dj=(2i3)i2j=0Dj2i2j=0jDj.

Ai:=ij=0Dj 以及 Bi:=ij=0jDj,則上式可以進一步寫成

AiAi1=(2i3)Ai22Bi2.

加上 Bi=Bi1+i(AiAi1) 這個條件,即可在 O(L) 時間內算出 DL=ALAL1

Subtask 4: L1010

我們再度化簡遞迴式:

{Di=(2i3)i2j=0Dj2i2j=0jDj,Di1=(2i5)i3j=0Dj2i3j=0jDj.

將兩式相減得到

DiDi1=(2i3)Di2+2i3j=0Dj2(i2)Di2=Di2+2i3j=0Dj,

因此

{DiDi1Di2=2i3j=0Dj,Di1Di2Di3=2i4j=0Dj.

再度將兩式相減得到

Di2Di1+Di3=2Di3,

故有

Di=2Di1+Di3. 這是個線性遞迴式。搭配初始條件 D0=1,D1=0,D2=1,則對於所有的 L3,皆有 (DL DL1 DL2)=(201 100 010)(DL1 DL2 DL3)=(201 100 010)L2(1 0 1). 利用快速冪,即可在 O(logL) 時間內得出答案。

留言

這個網誌中的熱門文章

2020 全國賽驗題心得

水母上 Divide-and-Conquer

109 學年度全國資訊學科能力競賽各題詳解