###### tags: `Algo` <style> /* basic design */ .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p { text-align: left; line-height: 1.8; letter-spacing: normal; text-shadow: none; word-wrap: break-word; } .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 {font-weight: bold;} .reveal section img {background:none; border:none; box-shadow:none; max-width: 95%; max-height: 95%;} .reveal blockquote {width: 90%; padding: 0.5vw 3.0vw;} .reveal code {line-height: 1.2;} .reveal p, .reveal li {padding: 0vw; margin: 0vw;} .reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 0.5vw 1.5vw 0.5vw 1.5vw; background: #EEE; border-radius: 1.5vw;} /* blockquote design */ .reveal blockquote { width: 90%; padding: 0.5vw 0 0.5vw 6.0vw; font-style: italic; background: #f5f5f5; } .reveal blockquote:before{ position: absolute; top: 0.1vw; left: 1vw; content: "\f10d"; font-family: FontAwesome; color: #2980b9; font-size: 3.0vw; } /* font size */ .reveal h1 {font-size: 5.0vw;} .reveal h2 {font-size: 4.0vw;} .reveal h3 {font-size: 2.8vw;} .reveal h4 {font-size: 2.6vw;} .reveal h5 {font-size: 2.4vw;} .reveal h6 {font-size: 2.2vw;} .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {font-size: 2.2vw;} .reveal code {font-size: 1.6vw;} /* new color */ .red {color: #EE6557;} .blue {color: #16A6B6;} /* split slide */ #right {left: -18.33%; text-align: left; float: left; width: 50%; z-index: -10;} #left {left: 31.25%; text-align: left; float: left; width: 50%; z-index: -10;} </style> <style> /* specific design */ .reveal h1 { margin: 0% -100%; padding: 2% 100% 4% 100%; } .reveal h2 { text-align: center; margin: -5% -50% 2% -50%; padding: 4% 10% 1% 10%; } </style> # 2019/11/16 --- ## 原出處 - [KUPC2017](https://atcoder.jp/contests/kupc2017), [official site](http://www.kupc.jp/#/2017/) - A: Credits - B: Camphor Tree - C: Best Password - D: Sanmoku - E: Treasure Hunt - G: encode/decode 2017 - H: Make a potion - [KEYENCE Programming Contest 2019](https://atcoder.jp/contests/keyence2019) - E: Connecting Cities - 新北市資訊學科能力競賽 - Tree Coloring --- ## A - Credits Greedy: 從學分大的課開始選 --- ## B - Camphor Tree ![](https://img.atcoder.jp/kupc2017/89231ce01bb22b6ca3730fbd50870480.png) 給 S,T,問 S 到 T 是不是往上的一條路 --- ![](https://img.atcoder.jp/kupc2017/89231ce01bb22b6ca3730fbd50870480.png) 觀察: 每往下一個點編號剛好/2,所以把 T 一直 /2 看可不可以變成 S 就好 --- ## C - Best Password 給整數 A 和字串 S,找出一個 hash 一樣的字串,讓長度越短越好,長度一樣的話字典序越大越好。hash: $$A^1 \times C[S_1] + A^2 \times C[S_2] + ... + A^N \times C[S_N]$$ --- ### 想法1 把 S 的 hash 算出來,那麼答案的字母應該是把 hash 值寫成 A 進位同餘 - ans[1] ≡ hash / A % A (mod A) - ans[2] ≡ hash / A / A % A (mod A) - ans[3] ≡ hash / A / A / A % A (mod A) - ... ```python hash = get_hash() / A while hash: c = hash % A while c+A<=26: c+= A # 得到最前面字母是 c hash = (hash-c)//A # 繼續求後面 ``` --- ### 想法2 - 也可以從第一個字元開始,每次把最前面字母+A,後面的字母-1 - 實作上有點像大數的進/退位 --- ## D - Sanmoku 問題: 在 $n \times n$ 的格子上填正整數,讓各個方向連續三格數字種類數至少有兩種。 - 要怎麼讓最大填的數字最小? - 在最小的情況,有幾種填法? --- 觀察: <table> <tr><td>1</td><td>1</td><td>2</td><td>2</td><td>1</td><td>1</td><td>2</td><td>2</td></tr> <tr><td>2</td><td>2</td><td>1</td><td>1</td><td>2</td><td>2</td><td>1</td><td>1</td></tr> <tr><td>1</td><td>1</td><td>2</td><td>2</td><td>1</td><td>1</td><td>2</td><td>2</td></tr> <tr><td>2</td><td>2</td><td>1</td><td>1</td><td>2</td><td>2</td><td>1</td><td>1</td></tr> <tr><td>1</td><td>1</td><td>2</td><td>2</td><td>1</td><td>1</td><td>2</td><td>2</td></tr> <tr><td>2</td><td>2</td><td>1</td><td>1</td><td>2</td><td>2</td><td>1</td><td>1</td></tr> <tr><td>1</td><td>1</td><td>2</td><td>2</td><td>1</td><td>1</td><td>2</td><td>2</td></tr> <tr><td>2</td><td>2</td><td>1</td><td>1</td><td>2</td><td>2</td><td>1</td><td>1</td></tr> </table> - 最大的數字小於等於 2 - n大一點的情況這樣填就好 - 填法好像只有幾種?? --- 填法真的很少所以可以爆搜。做法: 從左上到右下枚舉 1 or 2,每次往前面檢查有沒有衝突,有的話就剪枝掉 <table> <tr><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td></tr> <tr><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td></tr> <tr><td>.</td><td>o</td><td>.</td><td>o</td><td>.</td><td>o</td><td>.</td><td>.</td></tr> <tr><td>.</td><td>.</td><td>o</td><td>o</td><td>o</td><td>.</td><td>.</td><td>.</td></tr> <tr><td>.</td><td>o</td><td>o</td><td>?</td><td>.</td><td>.</td><td>.</td><td>.</td></tr> <tr><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td></tr> <tr><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td></tr> <tr><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td></tr> </table> --- <table> <tr><th>n</th><th>最小值</th><th>狀態數</th></tr> <tr><td>1</td><td>1</td><td>1</td></tr> <tr><td>2</td><td>1</td><td>1</td></tr> <tr><td>3</td><td>2</td><td>32</td></tr> <tr><td>4</td><td>2</td><td>18</td></tr> <tr><td>5+</td><td>2</td><td>8</td></tr> </table> --- ## E - Treasure Hunt 每個寶箱有相對應的價值,每個鑰匙能開兩個寶箱其中之一。問要怎麼開始得到的總價值最多? --- 把寶箱看成點,鑰匙看成邊。考慮每個連通塊,如果是樹的話: <center> <img src='https://i.imgur.com/zcimCFH.png'></img> </center> --- 假設每條邊都往下指的情況,只有根的寶箱沒選到(邊指的方向 = 開哪個寶箱)。 <center> <img src='https://i.imgur.com/t6SPBwd.png'></img> </center> --- 性質: 給你一棵樹,可以選任意點當根。 => 只有根節點的寶箱開不了 => 不開的寶箱是哪一個可以自己決定 <center> <img src='https://i.imgur.com/icHWKnk.png'></img> </center> --- 不是樹的 case: 所有寶箱都開得到 理由: 一定有一個點在環上(非樹邊),那就讓那個點當根結點其他點照樹邊開就好了。 --- 結論,對於每個連通塊: - 如果是一棵樹: 除了價值最小的寶箱都可以開 - 不是樹: 所有寶箱都可以開 對每個連通塊: - 判斷是不是樹 - 求出寶箱價值總和以及價值最小的寶箱 就好了 --- ## F - encode/decode 2017(1) 要你輸出一張圖,把一個整數 x encode 在裡面。條件: - 剛好有 100 個點 - 某些邊沒辦法輸出,不能輸出的邊剛好是一棵樹 - 不可以有自環或者重邊 --- <center> <img src="https://i.imgur.com/SCOuyxx.png"></img> </center> --- <center> <img src="https://i.imgur.com/sV1VJUM.png"></img> </center> --- - encode: 輸出一張 x 條邊的圖 - decode: 輸出邊數 AC --- ## G - encode/decode 2017(2) 同上,$x \le 10^{18}$ --- 第一個想法: 要怎麼壓一個 60 個 bit 的整數? 可不可以做一條長度 60 的 path,想個辦法把每個 bit 表示出來? --- 例子: 下面的圖是二進位的 1010,有分叉=1,沒分叉=0 <center> <img src="https://i.imgur.com/C7C1RdD.png"></img> </center> 問題點: 進到 decode phase 的時候編號會被打亂,要怎麼知道這個是 0101 還是 1010?? --- 改進: 我們在起始點加一個觸角,這樣就可以分別起點和終點了! 判斷方法: 全圖只會有一個點度數 >= 4 <center> <img src="https://i.imgur.com/Z8vLcHw.png"></img> </center> --- 分析 - 60個 bit 各一個點 - 每個如果bit是 1 的話要再加 1 個點,最多 60 - 另外 3 個點當觸角 (´-\`).。oO (當每個 bit 都是 1 的時候最多會要 123 個點) --- 改進: 舉例來說,當 1 太多的時候我們可以記錄一個符號,把 1 的 bit 和 0 的 bit 反過來紀錄 例子: 如果 p1 p2 相連的話代表求出來的 1/0 要反過來 判斷方法: 有沒有存在一個點數 = 2 的聯通塊 <center> <img src="https://i.imgur.com/YhIGTyU.png"></img> </center> --- decode 算法: - 找一個 degree>=4 的點當作起點 - 從起點找一條長度 60 的路徑,還原 60 個 bit - 再找圖上有沒有點數=2的聯通塊,有的話把上面 bit 反過來 分析2: - 60個 bit 各一個點 - min(#bit=0, #bit=1) <= 30 - 另外 3 個點當觸角 - 另外 2 個點紀錄有沒有相反 95個點 --- 其他: - 要怎麼樣求出一條長度 60 又根原本 T 不重複邊? - 可以 random 找 - 或者可以利用樹是二分圖的性質好好做(二分圖中,同樣顏色的點互不相聯 = 可以隨意加邊) - 其他解法不少,但大部分都非常難寫 --- ### 解法X - encode: 輸出一張0條邊的圖,把 X output 到 `/tmp/foo` - decode: 把 `/tmp/foo` 的數字輸出出來 Atcoder可以過,但是 domjudge 不能用。domjudge 好強 --- ### 值得一提的事情 ```bash "$MYDIR/conv1" < "$TESTIN" > "_in1" "$@" < "_in1" > "_out1" "$MYDIR/checker1" "$TESTIN" "_out1" "_out1" EXITCODE=$? if [ "$EXITCODE" -ne 0 ]; then echo "WA1" > "$PROGOUT" echo "======" >> "$PROGOUT" cat "_out1" >> "$PROGOUT" exit 43 fi "$MYDIR/conv2" "$TESTIN" < "_out1" > "_in2" "$@" < "_in2" > "_out2" "$MYDIR/checker2" $TESTIN "_out2" "_out2" EXITCODE=$? if [ "$EXITCODE" -ne 0 ]; then echo "WA2" > "$PROGOUT" echo "======" >> "$PROGOUT" cat "_out2" >> "$PROGOUT" exit 43 fi echo "AC" > "$PROGOUT" exit 0 ``` - 每一份 output 都有一個專屬的 checker - 每一份 input 也都有一個專屬的 converter - 為什麼第一份 input 也要 convert? --- ### judge的公平性 - 當參加者輸出同樣的東西時,理論上 judge 結果要是一樣的。 - 可是當要轉成 decode 測資的時候有隨機編號,有可能會造成某次傳 AC 某次 WA?? - 為了避免這樣的情況,其實重新編號的過程並不能是隨機決定的 - 或者說: 對於同一筆 input 以及同一筆 encode output,轉成的 decode input 必須是一樣的 為了避免這樣的情況發生,在每筆測資的後面會偷放一個 random seed。要重邊號的時候會按照這個 seed 去亂數。例如下面是裁判真正看到的 input: ``` 100 99 99 95 (中間省略) 22 30 317942985495155176 <= X 的值 56638772004192693 <= random seed ``` --- ### I. Tree Coloring(1) 給一個 n 層的樹,問 R,G,B 各有幾個(n <= 30) <center> <img src="https://i.imgur.com/H9NuQE9.jpg"></img> </center> --- 第 i 層 = $2^{i-1}$ 個節點 - R的個數: $2^{i-1}/3 + ((2^{i-1}\%3) \ge 1)$ - G的個數: $2^{i-1}/3 + ((2^{i-1}\%3) \ge 2)$ - B的個數: $2^{i-1}/3$ 每一層加起來就好 --- ### J. Tree Coloring(2) 給一個 n 層的樹,問 R,G,B 各有幾個(n <= 1,000,000,000) <center> <img src="https://i.imgur.com/H9NuQE9.jpg"></img> </center> --- - 總節點數 = $2^0 + 2^1 + ... 2^{n-1} = 2^n - 1$ - A = 節點數 mod3=1 的層數 = n/2 + n%2 - B = 節點數 mod3=2 的層數 = n/2 - 其餘 $2^n - 1 - A - 2B$ 個節點應該是均分在 R,G,B 三種顏色,所以除以 3 就好 - 要怎麼除以3? 模逆元! --- ## K - Connecting Cities(1) 最小生成樹直接做 --- ## H - Make a potion 見 [pdf](http://www.kupc.jp/static/media/H.ab00f1d5.pdf) p6,p7,p11 解釋: - f(i,j) = 第 i 種液體有沒有使用 j 體積以上,有的話=1,沒的話=0 - f(i,j-1) >= f(i,j) - 把所有 f(i,j) 當成點做一個最小割 - S集合 f(i,j) = 1,T集合 f(i,j) = 0 - f(i,j)點太多怎麼辦? 可以離散的求(對於液體i,只有最小、最大、以及每個和i有關的條件的體積有用而已) - 先假設正的效果全部拿到,減掉最小割=答案 - 加分加最多 = 扣分扣最少的技巧 --- ## L - Connecting Cities(2) 給 D,A,連接 i,j 的邊權是 $D|i-j| + A_i + A_j$。求這張圖的最小生成樹 --- ### 先輩知識1: MST性質 - 對於任意一個環,環上最大的邊一定不會是MST的邊(弱的環定理) - 如果有一棵生成樹T,加上任何一條未使用在T裡的邊。都能保證這條邊是環裡最大的話。則 T 是 MST(強的環定理) - 如果把一棵MST分成兩個連通塊 T1,T2。連接 T1,T2 的邊必須是兩聯通塊之間最小的 --- ### 先輩知識2: 線段樹 問題: 給你一個 i,要怎麼找出 j 使得 $D|i-j| + A_i + A_j$ 最小? - 分析1: - 當 $i \ge j$ 時,上式 = $Di + A_i - Dj + A_j$ - 當 $i \le j$ 時,上式 = $-Di + A_i + Dj + A_j$ - 假設 $v1[i]=Di+A_i$, $v2[i]=-Di+A_i$ - 對左邊的j,查詢 v2[j] 最小,邊權=v1[i]+v2[j] - 對右邊的j,查詢 v1[j] 最小,邊權=v2[i]+v1[j] - 可以用一棵線段樹來做 RMQ,每次查詢 O(log n) --- ### 做法1: 銀腦減邊法 - 性質: 假設 i 的 A_i 最大,則 i 只需要往左、右個連一條最小的邊就好 - 證明: 下一頁 演算法: 每次挑一個最大的 A_i,往左右個連一條最小的邊。接著把 Ai 刪掉,再找第次大的重複上面動作 只會加 2n 條邊而已,接著對這 2n 條邊做一次 kruskal 就好 --- ### 證明 - 假設 i 的右邊最小邊為連到 j,另外一條野生的非最小邊 k - dis(i,j) < dis(i,k): 我們假設的 - 接著只要證明 dis(j,k) < dis(i,k),就能證明 (i,k) 是不需要的邊(因為是 i,j,k 形成環中最大的) - 若 i < j < k - $A_j + A_k < A_i + A_k$, 因為 $A_i$ 是假設 A 最大的點 - |j-k| < |i-k| (|i-k| = |i-j| + |j-k|) - 由上面兩個得證,dis(j,k) < dis(i,k) - 若 i < k < j - 把 dis(i,k) > dis(i,j) 展開,可得: $A_k + Dk > A_j + Dj$ => $A_k - A_j + Dk - Dj > 0$ - dis(i,k) - dis(k,j) 展開,可得 $A_k - A_j + Dk - Dj$ - 根據上面,得知 dis(i,k) - dis(k,j) > 0 --- ### 做法2: Borůvka's algorithm 算法: - 一開始假設各個點都是一個獨立聯通塊 - while(連通塊個數) > 1,重複以下動作 - 對於每個連通塊,找一條連到其他連通塊最小的邊 - 如果加上這條邊不會形成環,加他,else ignore --- 性質: - 為什麼他是對的? - 因為prim是對的,上面算法很像「平行一口氣做很多個prim」 - 複雜度? - while(連通塊個數) > 1 這個迴圈只會進 O(log n) 次 - 因為每進一次,連通塊個數至少減半 - 迴圈裡面 O(m),所以複雜度 O(m log n) --- ### Borůvka 與這題的關係 前面的線段樹告訴我們,在 `while(連通塊個數)` 的迴圈裡,我們大概可以花個 O(n log n) 或者 O(n) 的時間找到每個連通塊向外連最小的邊 複雜度 O(n log^2 n) or O(n log n) --- ### 做法3: 樹分治 [link1(p165以後)](https://img.atcoder.jp/cf17-final/editorial.pdf) [link2(日文)](http://drken1215.hatenablog.com/entry/2019/01/15/081500) 想法和 Borůvka 差不多 --- ### 做法4: --- ### 為什麼會有這一題? 簡答: 教育意義重大,可以看出很多最小生成樹的有趣性質。有興趣的話建議每個做法都寫一次,可以多了解很多事情 啟示錄,一般來講這種題目的性質: - 有 n^2 條邊,而且 n 很大 - 對於每個點,可以快速求出與他相連最小的邊 - 例如這題就是用線段樹處理 滿足上面條件的圖,應該(?)都可以用上面的幾種作法處理 --- ### 例題 - [Connecting Cities(keyence 2019)](https://atcoder.jp/contests/keyence2019/tasks/keyence2019_e) - [Interval and MST(dowango 5th final)](https://atcoder.jp/contests/dwacon5th-final/tasks/dwacon5th_final_c) - [Tree MST(codefes 2017 final)](https://atcoder.jp/contests/cf17-final/tasks/cf17_final_j)
{"metaMigratedAt":"2023-06-15T01:41:23.528Z","metaMigratedFrom":"YAML","title":"2019/11/16","breaks":true,"slideOptions":"{\"theme\":\"white\",\"slideNumber\":\"c/t\",\"center\":false,\"transition\":\"none\",\"keyboard\":true,\"width\":\"93%\",\"height\":\"100%\"}","contributors":"[{\"id\":\"e69b0307-755d-4f0e-bba0-9d49724b9eb8\",\"add\":12876,\"del\":1193}]"}
    744 views