2015年4月12日 星期日

Codeforces Round #298 (Div. 2)

這場的題目敘述偏長有些題目都是閱讀題 ( 要自己根據題目發生的事件來判斷有哪些限制,如 pB 和 pD ),對英文不好的人大概很吃力吧 ...

Problem A --- Exam

題意:給找出正整數 $1 \sim N$ 的最大的 subset,使得可以把這個 subset 的所有數排成一列,使得相鄰兩個數的差的絕對值都不是 1,並給出任一種排列方法。
數據範圍:$N \leq 5000$。

tag:[構造]

= = = = = = = = = = = = = = =
身為 Div 2 A 的構造題,應該就是個隨便亂構造都構造的出來的東西吧?合理猜測大部分的 $N$ 值都有辦法找到一個 $1 \sim N$ 的排列滿題目條件,只有 $N = 2, 3$ 時例外。

我除了特例以外的構造方法可直接參考我的 code

Problem B --- Covered Path

題意:有一台車共行駛 $t$ 秒,第 $1$ 秒的速度是 $v_1$,第 $t$ 秒的速度是 $v_2$。每一秒內的速度都是等速,相鄰兩秒的速度差至多為 $d$,請問這 $t$ 秒鐘最長的可能行駛距離是多少?

數據範圍: $1 \leq v_1, v_2 \leq 100$,$2 \leq t \leq 100$,$1 \leq d \leq 10$

tag:[ 數學 ] [ 物理 ]

關鍵字:斜率
= = = = = = = = = = = = = = =
某種程度這題算是閱讀題吧?要確實理解題目所說的物理意義。若把此題的物理意義移除,則會變成:有 $t$ 個數 $a_1, a_2, \cdots , a_n$,已知$a_1 = v_1$,$a_t = t_2$,任相鄰兩數差都不超過 $d$,請問所有數的總合最大是多少? (你若問要是速度出現負數也能這樣轉變題目嗎?我並不清楚,題目關於速度是負的似乎沒寫清楚要怎麼考慮這樣的狀況。)

轉換完題目後就比較好思考了。我們會希望每個數愈大愈好,考慮 $a_1$ 及 $a_t$ 對所有其他數產生的限制會得到不等式: $a_i \leq a_1 + d * (i-1)$ 以及 $a_i \leq a_t + d * (t-i)$。並且直接令 $a_i = \min(a_i + d * (i-1), a_t + d * (t-i))$會滿足題目條件,於是得到能使這 $t$ 個數總合最大的序列了。

這個解法其實是很常見的概念,我印象中 SRM中 Div1 Easy 裡根本有簡化後一模一樣的題目。

另外,由於這題的數據很小,所以也可以使用 dp 來做,$dp[x][y]$ 用來儲存 時間 $x$ 速度為 $v$ 時從時間 $1$ 至時間 $x$ 所能行駛的最長距離。

Problem C --- Polycarpus' Dice

題意:有 $n$ 個骰子,第 $i$ 個骰子的可能結果為 $1 \sim d_i$ ,已知擲出來的骰子結果為 $A$,請對每個骰子輸出一定不可能骰出的點數有幾種。

數據範圍: $n \leq 200,000$,$1 \leq d_i \leq 10^6$,$n \leq A \leq d_1 + d_2 + \cdots + d_n$。

tag:[ 貪心 ]

= = = = = = = = = = = = = = =
個人覺得這題比 B 還要簡單耶?可是 B 的 AC人數還是多很多。這題 pretest沒有會爆 int 的測資,所以可以 hack 到大量只開 int 的解。

對於一個骰子,不難想像他可能骰出的值的範圍是連續的,所以只要求出他最低可能值和最高的可能值就結束了。最高的可能值就是其他骰子的值盡可能小的時候,也就是 $\min (d_i, A-(n-1))$,而最低的可能值則是其他骰子的值盡可能大時,也就是 $\max (1, A-(\sum_{x=1}^n{d_x}) + d_i)$

Problem D --- Handshakes

題意:有一個資訊競賽活動,每個新進場的人都要和在場的所有沒有正在比賽的人握手,且任何時刻在場的任三個人都有可能組成隊伍並開始比賽,直到活動結束,在活動結束前不會有任何人離開會場。給你每個人進場時各和多少數量的人握手,請判斷給你的資訊是否有某種符合的所有人進場順序,若有,請給出一組解。

數據範圍:人數不超過 $2*10^5$。

tag:[ 貪心 ]
= = = = = = = = = = = = = = =
這題我看了大家的 code 後發現有各種做法,我就講一下最多人使用同時也是我使用的方法。

一樣先簡化一下題目,把他變成比較正統的數學描述方法。
若把這些人的握手數按照進場順序寫下來($d_1 \sim d_n$),必須滿足以下三個條件:

1. $d_1 = 0$
2. $d_i % 3 = (i-1) % 3$
3. $d_{i+1} \leq d_i + 1$

我想了一下下後直覺告訴我這題是貪心的題目,若要使用貪心,一個很常見的方法就是,每次決定下一個數時,直接選擇最大的可以用的數,而在這題這樣子做會是對的,而且將有很好的複雜度。

實作方式如下:
1. 設一變數 $v$ 代表我們想要取的值,初始值為 $0$。
2. 若存在握手數為 $v$ 的人,則把他當作下一個進場的人,並且把 $v$ 值加一。
3. 否則把 $v$ 值減 $3$ 並回到步驟 2,若還沒取到 $n$ 個人 $v$ 值就變為負,則答案為 Impossible。
 可參考第一名的 code。

我們不需要擔心步驟 3 執行太多次,因為所有過程 $v$ 的減少量不會超過 $v$ 的增加量加二,而會增加 $v$ 值的步驟 2只會執行至多 $n$ 次,且每次只加一。

至於此方法的正確性嘛 ...有一類的貪心法證明是長這樣:若存在某個解答長的和我們構造出來的的不一樣,一定有辦法經過一些變換變成和我們構造出來的一樣,於是若存在解則我們的構造法一定也能找到解!

於是我們先假設,我們若找出的解答,則會是 $d_1, d_2, \cdots, d_n$,以及現在存在另外一組解答為 $a_1, a_2, \cdots, a_n$。
若序列 a 和序列 d 不同,必有一最小的 index $i$ 使得 $d_i \neq a_i$,並且由於 $d_i$ 是可以挑的最大的數,所以我們可確定 $d_i > a_i$,接著我們令 $j$ 為大於 $i$ 的最小的 index 使得 $a_j = d_i$,接下來我們想要選一個 index $k$,使得我們能把序列 $a$ 的第 $i$ 個數至第 $i+k-j$個數整段能無痛的和 第 $j$ 個數至第 $k$ 個數交換。舉個實例:

$d$ 序列為 0,1,2,3,4,5,6,1,2,3,1
$a$ 序列為 0,1,2,3,1,2,3,4,5,6,1

則 $a$ 序列和 $d$ 序列第一個不同的數的位置是 4,並且在 $a$ 序列中 $d_4 = 4$ 這個數下一次出現的位置是 $a_7$,而我們發現我麼可以把藍色這段和紅色這段交換就變成 $d$序列了!分 case 討論後可以證明,令交換的子序列為最長的子序列,滿足 紅色子序列的所有數比對應到的藍色子序列的數都還要來的大(嚴格),則一定可以交換。(我覺得兩個子序列緊接在一起的 case 並不太好證。但總之我確實把所有 case 都證過了。)

實際上並無法總是只交換一次就把 $a$ 序列變成 $d$ 序列,必須交換數次才能讓同樣的 prefix長度增加為序列長度。

徵求精減的證明 >_<。

Problem E --- Berland Local Positioning System

題意:公車站共有 $n$ 站,公車會從第 $1$ 站出發直到第 $n$ 站,再折返回第 $1$ 站,並一直反覆。有一個人他把上車到下車時所經過的所有站的編號記下來 (包括首尾站,並且他可能繞了好幾圈才下車),但你只知道這些邊號排序後的結果,給你每站之間的距離,請問你是否能確定這個人搭了多長距離的公車?

數據範圍: $n \leq 2*10^5$,給的編號序列一定合法,序列長度為 $m$ ($1 \leq m \leq 4*10^5$)

tag:[ 模擬 ] [ 兩個指標 ]

= = = = = = = = = = = = = = =
賽中我花在 hack 的時間太久,回來寫這題時已經來不及寫不完了@@賽後也懶的寫完...

我想到的方法如下:

令 $d_i$ 為數字 $i$ 在序列中出現幾次,則任意合法路段,把序列 $d$ 按照連續相同的數字分段,一定不會太多段。於是我們可以用一些 tuple $(L ,R ,num)$ 來代表邊號 $L \sim R$ 的公車站都個別出現 $num$ 次。

所以我們就枚舉搭公車的起點,在枚舉開始方像為左或右,由於我們知道他搭公車的站數為 $m$,所以可以快速求出上一段所說的行式,就可以比對看看和這個人所搭的公車有沒有一樣。一樣的話就可以檢驗此種公車路線長度是否和至今合法的路線長度都一樣。

這個做法細節想起來有點複雜,會令人不想寫下去,,,

現在我們再多想一點點。

若先隨便選個點當起點,並經過 $m$個站,我們就得到了其中一種經過 $m$ 佔的公車路線。接著,我們只要把此路線的起點向前移一站,同時也把終點向前移一站,就得到另外一個公車路線了!共移 $2n-3$ 次就可以把所有可能的公車路線都試過了 =口=

於是我們可以輕鬆的使用 map 等資料結構來記錄路線的狀態有沒有和題目所給的序列一樣。

以上第二個方法是我看了第一名的 code 才發現的。

Problem F ---  Simplified Nonogram

題意:Nonogram是個類似數讀的遊戲,以一個 $n*m$的表格,請你把每一個都塗成黑色或白色,使得每一行及每一列連續的黑格段數都和測資指定的一樣。任一種填法皆可。

數據範圍:$1 \leq n \leq 5$,$1 \leq m \leq 20$。

tag:[ DP ]

= = = = = = = = = = = = = = =
我看到這題就立刻聯想到我在 SRM655 出的 Div.2 Hard,畢竟數據範圍長的那麼像...,於是我就直接從 dp 的方向去思考了。這題比我出的那題麻煩,還要輸出解...,很難讓不同層的 dp 共用記憶體。我用來儲存代表每一列的狀態數有 $21$ 那麼大,記憶體若要開 $21^5 * 20 * 2$ 不知道是否開的起來...,但我想說應該亂寫都會過吧,就不開陣列來 dp 而直接使用 STL 的 map。但寫完後使用 Codeforces 的 Custem Invocation 測了一下發現會 TLE ... 最後靠著 "用非常多map代替一個 map" 以及 "一定沒有必要走到的 status 都不要塞到 map裡" 就 AC 了。其實應該只需要靠第二個方法,因為它能減少大量的狀態數。

賽後開了別人的 code 發現,直接開陣列記憶體其實有辦法開的起來,,, (只是要想辦法只使用bool 或 char)。

我 dp 的方式是以 column 的順序 dp,每加一個 column 就更新每一個 row 的狀態,每個 row 的狀態有:已有幾條連續的黑格,以及最後一個是黑的還白的。應該算是平淡無奇吧?就狀態維度有點多令人感到有點煩而已。

而我所位的一定沒有必要走到的狀態是指:若存在某一個 row ,後面無論如何塗黑白格都無法達成目標的狀態。

2015年4月5日 星期日

ZeptoLab Code Rush 2015

這場比賽是 Div.1 和 Div. 2 一起比的,而且共有七題,比 2.5 個小時。

賽中我只寫了前四題 OAQ 賽後賭氣似的把所有題目都寫了一遍,所以這算是近期頭一場我真的有親自寫過所有題目的Div. 1 Codeforces Round。

點我連到官方 Editorial

problem A --- King of Thieves

題意:給一個僅包含 '*' 和 '.' 的字串,請問是否存在恰 $5$ 個 '*',他們的位置是成等差數列。

數據範圍:字串長度不超過 $100$。

tag:[ 模擬 ]

= = = = = = = = = = = = = = =
有各種做法,我的做法是枚舉等差數列的開頭位置和公差,逐一判斷。

Problem B --- Om Nom and Dark Park

題意:給一個完全二元樹,每個邊上都有一個正整數的權重,現在你要把一些邊的權重加上一個正數,使得所有 root 到 leaf 上的的路徑權重合都相同,請問所有邊要增加的值至少多要多少?

數據範圍:樹的深度至多為 $11$。

tag:[ 貪心 ]

= = = = = = = = = = = = = = =
tag 設為 [貪心] 可能有點怪怪的,還是要把他當作數學啊?

從 root 到每個 leaf 的路徑上權重合都一樣,可推得給定任一點 $x$,則 $x$ 到所有在他 subtree 下的 leaf 的路徑上的權重合也都一樣。所以我們難免想要從比較小的 subtree 先考慮。

使用數學歸納法的方式去思考:
現在我們先考慮只有三個點的完全二元樹,他只有兩個邊,根據條件這兩個邊值必須相同,那顯然最好的情況就是把比較小的那個邊加上兩個邊的權重差的絕對值。

接著我們考慮有 $2^n-1$ 個點的完全二元樹,我們先個別把 root 的左子樹和右子樹(也就是大小為 $2^{n-1}-1$)用遞推的方式各別解決,然後把 左/右 子樹的 root 到 leaf 的路徑權重合加進 root 連到 左/右 子樹的邊裡,把他想像成只有三個點的完全二元樹去解決就行了。

所以這題的時間複雜度是 $O(n)$。

Problem C --- Om Nom and Candies

題意:給五個正整數 $C,x1,v1,x2,v2$ ,請求出非負整數 $a,b$ 滿足 $a*x1+b*x2 \leq C$ 且 $a*v1 + b*v2$ 最大。

數據範圍:所有數不超過 $10^9$。

tag:[ 數學 ]

關鍵字:根號
= = = = = = = = = = = = = = =
這是個很常見的題目,例如說曾出現在 2011年上海的 ICPC regional,所以應該是一堆人都見過的題目,我也曾經很仔細的想過這題,印象中實際上可以做到 O(log(數據範圍))。我隨意找了一個 Blog大家參考看看根號數據範圍的做法吧。

而且 code 可以寫的非常簡短:rng_58 的 code (rng_58的說明)

Problem D --- Om Nom and Necklace

題意:給一個字串和一個正整數 $k$,問有哪些長度的 prefix 是形如 $A_0B_1A_1B_2A_2..A_k$,其中所有 $A_i$都是相同字串,所有 $B_i$ 也是相同字串,且 $A_i$ 和 $B_i$都可以是空字串。

數據範圍:字串長度不超過 $1,000,000$。

tag:[ 字串匹配 ]

= = = = = = = = = = = = = = =
賽中我看完題目後,列了一些字串就直接猜到結論了。所是用猜的,但也並不難證明。

結論:所有滿足題間的 prefix,都是某個基本的字串 $s$ 重覆貼個 $k \sim k+1$ 次貼出來的,貼的第 $k+1$ 次可以只貼 $s$ 的 prefix,例如說 $s = "abc",k = 2$ ,那麼貼出來的字串可以是 $abcabc$、$abcabca$、$abcabcab$、$abcabcabc$ 四種。

證明分兩個方向,首先,若把 $A_iB_{i+1}$ 視為 $s$,則可推論所有滿足題目條件的 prefix 都一定由某個字串貼出來。接著令一個方向,設 $s$ 長度為 $L$,且貼的第 $k+1$ 次貼的長度為 $r$ ,則令 $A_i$ 為 $s$ 長度為 $r$ 的 prefix, $B_i$ 為剩餘部分,就能證出所有由 $s$ 貼出來的字串都會是滿足題目條件的 prefix。

知道了這件事後要怎麼做呢?

作法大至上分為 Hash 派、kmp 派、Z algorithm 派。
我不喜歡 Hash,因為 Hash有可能會被像我一樣的邪惡人士構造測資而被 hack,我也不喜歡 kmp,我覺得 kmp 很深奧 0.0 所以這類的題目我都使用 z algorithm。

使用 Z algorithm 後可以知道對於字串 $S$ 的每個 index $i$ ($0$ - base),都能求出最大的 $Z_i$ 使得 $S[0...Z_i-1] = S[i...i+Z_i-1]$。
如此一來我們就可以知道當 $s$ 長度選作 $i$ 時,可以貼出的長度有哪些了。

Problem E --- Transmitting Levels

題意:給 $n$ 個正整數 $a_1, a_2, ... , a_n$,這 $n$ 個正整數是環狀排列的。有 $q$ 個詢問,每次詢問會給一個 值 $b$,問要如何把這 $n$ 個數切成盡量少段,使得每一段數字和都不超過 $b$。

數據範圍: $n \leq 1,000,000$,$q \leq 50$,$a_i \leq 10^9$,$b \leq 10^15$。

tag: [ 貪心 ]

關鍵字:環狀貪心
= = = = = = = = = = = = = = =

如果不是環狀,這題大概只有 Div.2 pB 的程度而已,只要從最左邊開始,每一段都取盡可能的長,不能再取時就把下個樹當作下一段的開頭。

變成環狀後要怎麼處理呢?直覺的方法就是枚舉開頭位置,然後作和非環狀一樣的事,但這樣顯然太慢了。

首先,至少我們都能夠 $O(n)$ 預處理求出,由每個 index $i$ 當開頭,最長的一段能多長(暢度記為 $l_i$ )。

接著,以下提供三種思路:

1.
無論從哪個位置開始,總是有一個時候會跨過第 $n$ 個數和第一個數之間 (我們把某一段恰結束在第 $n$個位置也列入考慮),並且,我們就直接把跨過去的那一段當作最後一段。

若這樣想的話,我們就可以從後面 dp 回來了!dp 時要記錄兩個值,分別是,由某個位置當作開頭貪心去分段,要分段直到跨過去時,共分了幾段,以及最後一段結尾的位置。如果某個位置至結尾位置長度為 $n$ 以上,那我們就更新答案。此做法應該是相當好想好寫的一種。

關於這個做法,我覺得正確性並不明顯,有人和我相同感覺嗎@@?

2.
把 $l_i$ 最小的那一段挑出來(記長度為 $L$ ),那麼無論從哪裡當作起點 greedy,一定會跨過該段,所以我們就不妨直接枚舉那一段每個位置當作起點 greedy 去劃分段落,由於每一段長度都至少為 $L$,所以至多只需要 $O(n/L)$ 就可以知道由某個位至當作起點答案是多少,總共枚舉 $L$ 次,故每個詢問時間複雜度都是 $O(n)$ 。

這是Editorial 給的方法。

3.
若現在不是問你最小的環狀分段法,而是問你從所有位置切下去變成不是環狀後,最小的分段法個是如何。那麼以上兩個方法都不能用了。而這個方法仍可解決,但多了 union find 的時間複雜度。

首先把這 $n$ 個數字重複共貼 $3$ 遍,例如說 $1, 2, 3$ 就會變成 $1, 2, 3, 1, 2, 3, 1, 2, 3$。

以 $b = 4$ 作舉例,我們把所有 index 都當成是一個點,並且每個點都會向右連出去一條邊,連到以該 index 為起點,最長的段落能到達的右界的點,如下圗:

若一直把 $n$ 個數貼下去,將會是個無限大的有向森林。

我們若有知道從某個數當開頭,至少要幾要切成幾個段落,就等於在這張圖上問,從某個點當起點,至少要經過幾條邊才會跨過至少 $n$ 個點。

現在問們要使用 Union Find 來解決這個問題。在 Union Find 的過程中,我們還得記錄每個點到其所在 group 最遠的點的用通過的邊數。

Union Find的順序就由左到右一個一個加把邊兩個端點 Union 起來,以上圗來說,就是綠邊 => 藍邊 => 紅邊 => 綠邊 => 紅邊 => 藍邊的順序。我們只須考慮前 $2n$ 條邊。並且當我們union完第 $k$ 條邊時,我們就會發現第 $k-n+1$ 個點所記錄的到該 group 最遠的那個點所需的邊數,就會是由該點所代表的數開始當第一段,greedy 出來的答案。

Problem F --- Pudding Monsters

題意:在 $n*n$ 的表格中,每行每列都恰有一個擺有布丁,問有多少 $k*k$ 的子表格,也是每行每列恰有一個布丁($k$ 可以是任意數)?

數據範圍: $1 \leq n \leq 3*10^5$。

關鍵字:[ D&C ] [ 資料結構 ]

= = = = = = = = = = = = = = =

唔... 關於這題的解法我沒有特別想講什麼...,我賽後寫的方法完全參考 Editorial。

我賽中一直往資料結構的方向去想,可是我的腦袋對稍微複雜的資料結構實在是沒轍,但賽後看到了另外一個關鍵字:[ Divide and Conquer ] 就如同吸血鬼被打樁一樣... 因為我連想到了這題: sgu 512 Friendly Points,以 D&C 的做法而言這兩題超像啊....,同時 friendly Points 的類題也是日本 JOI 2013/2014 三模的題目かかし (日文解答)。過去雖然我知道有這麼一題存在,可是從來沒有寫過 Orz。

Problem G --- Spiders Evil Plan

題意:給一個每邊都有給長度的 Tree,有好多個詢問,每個詢問有兩個數 $x, y$,你必須回答,至多 $y$ 條 path 所覆蓋的聯通的邊集,且包含點 $x$,則邊集的總長度最大為多少?每個詢問回答玩才能得知下個詢問是什麼。

數據範圍:點數、詢問數不超過 $10^5$。

tag:[ 貪心 ]

= = = = = = = = = = = = = = =
我賽後才看了這題的題目,看完題目後的感想:這題不就是 POI 的題目在稍微變化一下而以嘛 = = 如果我在賽中看了這題而且 pretest 夠強的話應該能在賽中 AC 吧 @@
POI的相關題目是 POI 13th  stage 2 Subway,也可以參考 HOJ 101 捷運路線,和這題的差別就是沒有多組詢問,也沒有一定要包含某個點 $x$ 的條件。

有沒有做過 POI 那題真的對思考這題的時間差很多,另外 POI那題也和 Codeforces gym100020裡的 pH --- Tree 大有關係。Tree 這題問的是,給一個有根數,每次可以從root問某個葉子走下去並把所有邊塗色,問塗 $k$ 次至多能讓多少邊被塗上顏色。我會說 Tree 這題是 POI subway 的簡化版。

關於捷運路線這題可參考 這裡

接下來我要說的是我解這題的方法的概念,實作細節我就不提了...

首先,把樹的中心求出來當作根,接著從找出離跟最遠的葉子,並把根連到葉子的路徑拔掉,之後會變成森林,在一值重複把森林中跟到葉子最遠的路徑拔掉,直到所有邊都拔完為止。如右圖,整張圖就被分成了 $8$ 條路徑。

對於每個詢問 $x, y$,如果點 $x$ 包含在最長的前 $2y$ 條路徑裡面,那麼答案就是前 $2y$條的路徑和。
否則我們就強制在最長的前 $2y$條路徑中,在多連一條與前 $2y$ 條路徑包括 $x$ 的最長路徑,如下圖, $y = 2$,最長的四條為非黑色的路徑,點$x$是圖上標記的紅色的點。顧我們把紅點連到綠點的綠色路徑也加進去,於是目前就滿足了包括點 $x$ 的條件,但是現在多了一條路徑,於是我們必須要刪除一條路徑,而刪除的路徑我們只須考慮兩種選擇,第一種是藍色路徑綠點一下的半截,第二種事原本 $2y$ 條路徑中最短的那條(紫色路徑)。

我想這個做法是非常直覺的,但證明和實作上就必須加把努力囉。

2015年4月1日 星期三

比賽debug側記---把自己的思緒說給別人聽的好處

這篇也是紀錄有關某場比賽的事,不過只特別記錄其中一件插曲。

應該有不少人都聽過黃色小鴨除錯法吧?我個人相信這招對 debug 真的很有用,由其是在正式比賽中心浮氣躁的情況下用這招效果更是顯著 (把隊友當成黃色小鴨之類的),常參加團隊比賽的人或許多少都有這種經驗:當自己debug許久但都找不到bug,於是就要把自己的作法說給隊友聽,請隊友幫忙debug,但說著說著忽然自己就發現 bug 了!這個時候,其實你的隊友的地位就和黃色小鴨一樣而已XD畢竟他什麼都還沒做,只是在用耳朵聽你說。

3/29(日) 受人邀請組隊參加了 UTPC,是個主要給日本人參加的比賽(題目都用日語),不過藉由翻譯功能,多少還是能看得懂題目,並且真的讀不懂的地方可以向日本人的隊友確認。

但這場比賽我的表現實在欠佳,不知是太久沒比團隊比賽還是對第一次組的隊伍不習慣,總之這場我只寫了兩題 pD,和 pH,不過隊友們很罩就是了,最後還使勉強上了記分版上的第三名,唉唉不過大家看到我們這隊的組成應該還是會覺得比的不夠好吧 XD

這場比賽題目共分成 100 分、200 分、300 分三種,照記分版來看 pD 應該是 200 分以下最麻煩的一題,但實際上難度頂多只有 SRM Div.1 500或Codeforces Div.1 pC 左右吧,這裡就不提他了,我要談的題目是 pH --- 回すだけ,是個互動題。整場比賽我花了將近兩個半小時在上面 ...

題意:平面座標上有一個凸多邊行,其中一個頂點座標是 (0, 0),其他頂點座標你都不知道,你只確定它至多 $10$ 個頂點,每個頂點座標皆為整數,並且所有內角都在 170 度以下。現在你可以問至多 $1000$ 個問題,每個問題都形如下:"把此多邊行繞著原點逆時針轉 $x$ 度後,y座標的最大值是多少?"

互動格式:當要問問題的時候,以一個 '?' 加一個空白作開頭,後面接一個浮點數 $x$ 代表你所問的問題中的 $x$。當要回報答案時,答案有 $n+1$ 行 (n為凸多邊形頂點座標),每行都以 '!' 加一個空白做開頭,第一行僅包含一個數 $n$,第二行之後,由 (0, 0) 開始往逆時針回報所有頂點座標。

這題應該不太難,我想了幾秒就想到一個做法:每一個詢問等於我們可以知道這個凸多邊形在哪個半平面裡,並且我們若以 $1$ 度為間隔詢問 $360$ 次以上,那麼原本的凸多邊形頂點,一定在這$360$ 次詢問所得到的半平面的交集的凸多邊形頂點上。那我們就大膽的相信,求半平面交後的凸多邊形上的所有整數座標頂點搜集起來後,就是答案。

實際上有更精簡的做法,不需要使用半平面交,只需要求線段交點就有辦法得到答案,但這裡就不提了,畢竟我的重點是在於這題的 debug 。

總之這題就解法而言其實相當單純,但麻煩的就是很難測試自己程式碼的正確性,它的 Sample 很不親切,完全就只有格式上的舉例而已,於是我就把我的程式分區塊,用我自己的方法檢驗其正確性,大致上無誤後就 submit 了,但是卻 WA了 ... 我再仔細看了一次題目,發現要輸出 $n$ 但我沒有輸出,但是我改了這個 bug後還是 WA。

接下來我又測了很多東西,但真的完全找不到錯誤,於是我又完全改寫做法,改用不需要半平面交的做法(畢竟半平面交我是直接使用 codebook ,很擔心 codebook 其實是錯的 Orz),但還是 WA 掉了,甚至拿到 TLE,真的是莫名其妙呀...一直在考慮要不要把互動的 Judge 程式也寫一份來對拍看看是否正確,但是覺得好麻煩就放棄了。

我就這樣測了好久好久 ... 後來隊友來問我這題到底盡展得如何,我就開始向隊友說這題我實際採用的方法,以及我覺得可能出錯的地方和疑點,說著說著我突然發現,問問題時前面要加 '?',但是我卻加成 '!' 啊...該不會我 debug 那麼久就只是因為這樣吧?之後修掉這個 bug 後就 AC 了。並且實驗證時,AC的八十分鐘前上傳的 code也是修掉這個 bug 就能 AC。

真的非常對不起這場比賽的隊友們 OAQ