CSES Additional Problems Solution
前言
應該很多人都知道 CSES problemset 吧,裡面基本上就是經典題大雜燴。而 additional problem 是裡面的最後一章,也是很多人容易遺忘的章節。
個人認為前面章節的題目都算是對應章節的裸題,主要只是讓你知道這類題目最基本的樣子而已。所謂經典技巧當然不是只有這些裸題,有更多經典技巧是只能靠刷題來累積的,additional problem 裡就直接幫你整理出了一大堆,讓你不用從茫茫題庫中尋找,還不會被大主題直接爆雷。除了經典技巧外,這裡一些有趣的梗題與不有趣的構造題,個人還蠻喜歡的。
那這麼好的題單卻查不到一份完整的題解(註:在 2021 年,我不確定現在有沒有了),真是太讓我難過了,所以我就開始寫這篇題解。
順便先提一點,additional problem 的題目不一定會比前面的章節難,裡面也有許多簡單題,可以不用害怕,從解題人數多的題目開始下手,也不用把前面都刷完再來寫這部份。
推題
開始之前先推些個人覺得很棒的題 > <
Movie Festival Queries
Stack Weights
List of Sums
Binary Subsequences
Programmers and Artists
Removing Digits II
Counting Bishops
Missing Coin Sum Queries
Creating Offices
Permutations II
1087 - Shortest Subsequence
題解
一個直觀的 greedy 法就是從左掃到右,沿途計算目前四種字元的數量
若四種字元的出現過了,代表答案長度一定需要 +1,否則就會出現 subsequence
所以就把當前跑到的字元加到答案裡,並將數量歸零
跑完後要記得加上一個沒出現過的字元
1146 - Counting Bits
題解
先分別計算第 0 個 bit、第 1 個 bit、… 的答案,再加起來就好
觀察一下可以發現第 個 bit 每 個數會循環,且每個循環節前 個數會是 0,其他會是 1
所以對每個 bit 直接算出循環的數量,再加一加就能得出答案了
1670 - Swap Game
題解
遊戲最多只有 9! 種狀態,所以就蓋一張 9! 個點的新圖,將每種能互相到達的狀態用一條邊連接,然後再跑一遍 bfs 即可
小細節是要如何快速把一個狀態轉成一個數字,如果用 map 存可能會因為常數太肥而吃 TLE,這裡可以使用類似 hash 的方式,若這個狀態為字典序第 大的狀態,那就把它的編號定為 。編號加密與解密可以在 的時間內做完。
1134 - Prüfer Code
題解
首先一個顯然的性質:沒被列在當前 Prüfer Code 裡的點一定是葉子
所以做法其實很單純,跑過一次陣列,每次挑出一個 index 最小的葉子跟陣列的點連邊
若連邊後該點已經不在 Prüfer Code 裡了就能夠將它視為葉子
整個過程可以用一個 priority queue 處理
1756 - Acyclic Graph Edges
題解
把編號小的指到邊號大的,出來的圖就一定無環
2177 - Strongly Connected Edges
題解
結論
- 圖不連通或是圖上有橋,則無解
- 否則蓋一棵 dfs 樹,把樹邊的方向定為 parent to child,回邊訂為 child to parent,這樣一定是對的
證明
有橋無解我覺得算 trivial,就不提了 =w=
圖上沒有橋 -> dfs 樹每一條樹邊一定會被一條回邊蓋到 -> 必定可以從一個點走到根
走到根之後就能直接經由樹邊走到所有點了
2179 - Even Outdegree Edges
題解
結論:若有一個連通塊的邊數為奇數就無解,否則一定有解。
證明:首先因為一個連通塊的 out degree 和一定跟該連通塊的邊數相同,所以若邊數為奇數顯然無法讓該連通塊所有點的 out degree 皆為偶數。
而再來對於偶數的 case,我們用構造來證明。先只考慮一個連通塊且樹的 case,首先先隨便抓一個點當根開始 dfs,令 dfs(v) 會將子樹 的所有點(但不包含 )變偶出度,且會回傳 的出度是奇數還是偶數。
這樣一來,對於一個點 ,它的兒子 會有兩種 case:
- dfs(u) 是 1,就將邊從 指向
- dfs(u) 是 0,就將邊從 指向
這樣就可以讓兒子 的出度變成偶數。
而因為整棵樹的邊數為偶數,所以最後根的 dfs 回傳值一定是 ,也就代表整個構造是正確的。
再來考慮一般圖的 case,會發現前面的方法還是可行的,只要將回邊的方向全部定為 parent to child 即可,這樣就不會動到該點子樹的奇偶性
2422 - Multiplication Table
題解
對答案二分搜, 的數量為:
直接算就好
1142 - Advertisement
題解
經典題,叫做最大矩形覆蓋的樣子(?
先計算出 代表從最大的 index s.t.
再算出 代表最小的 index s.t.
這個可以用 stack 做單調隊列算出來,這裡講一下建出 的做法:
首先我們觀察到若 ,則 一定不會是之後的答案(因為 會先被 擋掉)
所以我們可以用一個容器存下還有可能作為答案的 index
每當加入一個數,就看它可以讓哪些位置不再作為答案( 的點),把它一個一個移除掉
結束之後就直接取在容器中最大的 值,它就會是我們要的 了
注意這整個過程容器需要的操作:
- 找出在容器中的數字中,最大的 值
- 加入一個數
注意到加入的數字的 值必會比當前在容器中所有數字的 值大,所以只需要用一個 stack
過程中維護出 top 作為當前容器的最大值即可
至於陣列 想法跟 類似,就留給讀者自行思考吧~
算完之後答案會是
怕我講的不太清楚,順便附上建造 陣列的code:
1 | vector <int> l(n); |
2186 - Special Substrings
題解
先幫每個出現的字母定義一個 hash 值吧
須滿足每個字母的 hash 值總和為 0
這樣一來問題就轉換成有幾個區間和為 0
套前綴和後變成問有幾對數字相同,可以直接用一個 map 做完
非 hash 作法沒多想,不過我翻每個人的 code 也幾乎都是 hash 就是了 =w=
2229 - Permutation Inversions
題解
代表長度為 、有 個 inversion 的 permutation 數量
則轉移式可考慮窮舉 所在的位置:
其中 為增加的 inversion 數量
一段區間和,可用前綴和加速,複雜度
1655 - Maximum Xor Subarray
題解
首先定義 代表前 項 xor 的和,則答案等於 的最大值
首先先把所有前綴值建一棵 0-1 trie
接下來要 query 一個數跟 trie 上所有數的最大 xor 值
由於二進位的性質,若可以先讓最高位變成 1 一定會較好
所以可以在那棵 trie 上尋訪,若原數該 bit 為 0,就優先選擇該 bit 為 1 的點,反之亦然
之後取全部的最大值即可
1664 - Movie Festival Queries
題解
來考慮一下倍增吧
代表從時間 開始,看 個movie的最短結束時間
倍增的轉移:
之後每次 query 就從最大的 bit 開始看,看跳完會不會超過限制
如果不會超過就更新答案即可
1697 - Chess Tournament
1702 - Tree Traversals
題解
preorder: 根 - 左子樹 - 右子樹
inorder: 左子樹 - 根 - 右子樹
postorder: 左子樹 - 右子樹 - 根
做法就是:
- preorder 的開頭是根,在 inorder 找到根所在的位置
- 現在知道左子樹在 preorder 和 inorder 的區間了,所以遞迴下去
- 同樣道理,遞迴右子樹的區間
- 輸出根
1704 - Network Renovation
題解
結論
令葉子數量為 ,則答案為
連邊的方法為將葉子按照以不為葉子的點為根做 dfs 的時間戳記排序好,將第 個葉子連到第 個葉子
若 為奇數,再將第 個葉子連到第 個葉子即可
證明
首先,由於每一條邊最多只會讓兩個葉子滿足條件,所以答案會有下界為
接下來就只要證明這樣的連邊方法是好的
首先,對於每一條邊 ,假設 為深度較淺的點,我們可以將葉子分成 三類:
- 集合 存時間戳記在 之前的葉子
- 集合 存在 子樹底下的葉子
- 集合 存時間戳記在 之後,但不在 的葉子
接著,我們想要證明若 或 ,則至少有一條從 到 的邊或從 到 的邊。
WLOG,
定義 為第一個 的點,第一個 的點,注意到 所以 一定存在。
則以下分為三種 case:
- 連到集合 的點,則與 連邊的點必屬於 集合
- 連到集合 的點
- 連到集合 的點,則與 連邊的點必屬於 集合
而因為我們拿非葉子的點當根, 一定有一個會非零,也就代表每一條樹邊一定會被一條新建的邊覆蓋。
1707 - Graph Girth
題解
對於每個點,我們計算出經過他的最小環,這樣再把全部取最小值即可
而求出最小環的方法就是跑一次 bfs,若遇到已經跑過的點就代表環產生,就直接更新最小值
這個做法的複雜度是好的原因是在本題的限制下, 不會爆
順便提一個最常見的假解,因為我自己也被坑過 XD
作法:從一個點開始做 dfs 樹,對於每一條回邊 到 ,將答案和 取最小值,其中 為 的深度
hack test:
假設以 1 為根,dfs tree 為 1 - 2 - 3 - 4 - 5
那這樣最小環會是 3,然後假解計算出來會是 4
1740 - Intersection Points
題解
經典的 sweepline + 資結題
假設有一條垂直的直線,他會從左邊跑到右邊(?
那在這過程中可能會有三種情況
- 遇到一個平行線段的起始點
- 遇到一個平行線段的終點
- 遇到一個垂直線段
其中當遇到 3. 的時候,會需要詢問當前的一段區間有幾條線段經過
而遇到 1. 2. 的時候,會需要將一個點加值或減值
而這些操作用一棵 BIT 可以做完
2214 - Inverse Inversions
題解
首先,先定義一開始的陣列
注意到我們每次將一對 交換的話,inversion 數量會加 1
這樣可以得到一個暴力解:先將 慢慢往前搬一格,直到沒辦法搬就換成把 往前搬,以此類推
因為每次往搬一格可以讓 inversion 數量加 1,所以一定會有一個時刻剛好有 個 inversion,到這時就可以停止了。
然而這樣複雜度會是 ,但優化方法也很簡單:只要事先算出每一個數字可以往前搬幾步多少就好了。
2215 - Monotone Subsequences
題解
結論: 無解,否則可以先構出:
再將 的數刪掉即可
證明:假設存在一個長度 的排列 ,它的 lis 與 lds 長度皆最多為
(註:lis -> 最長遞增子序列 lds -> 最長遞減子序列)
對於每個位置 ,紀錄 代表以 為結尾的最長 lis 長度, 代表以 為開頭的最長lds長度
那由於
由鴿籠原理得知必存在兩數 使
那接著分一下case:
-
:
此時 顯然矛盾 -
:
此時 顯然矛盾
故假設錯誤
1743 - String Reorder
題解
首先先講一個顯然的結論,一個長度為 的字串可以被 reorder 的條件為:
出現最多次的字母數量
有了這個就可以導出之後的 greedy 策略:
每一次都選擇放置之後剩下的字元還能 reorder 的最小字母即可
2425 - Stack Weights
題解
首先先講一個結論:
定義 為放置在左邊且編號 的數量, 為放置在右邊且編號 的數量
則若左邊的重量確定小於右邊須滿足:
原因:要是存在 滿足 ,則將編號 的重量訂為 ,編號 的重量訂為 即會使左邊較重
這樣就導出一個做法:
若是我們計算出 ,則
- ,則代表右邊較重
- ,則代表左邊較重
- 無法分辨
所以就開一棵存取最大值和最小值的線段樹,放入一個硬幣就相當於對一個前綴 +1 或是 -1
之後就可以用全域的最大值和最小值判斷大小關係
1747 - Pyramid Array
題解
可以發現最小的數字一定會出現最左邊或是最右邊
那把這個數字拔掉之後,剩下的數字中的最小值也會在最左邊或最右邊
所以這就導出一個做法:
- 將數字從小到大看
- 對於當前的數字,計算出他左邊的數量和右邊的數量
- 取兩者最小值,並把該數從陣列中刪除
而對在位置 的數來說,左邊的數量其實就是 - 左邊被刪除的數字,右邊同理
所以可以用一個 BIT 快速算出數量
1748 - Increasing Subsequence II
題解
令 為陣列前 項的答案,可以推出一個很顯然的轉移式:
一段前綴,可以先把數字離散化之後套 BIT 或線段樹來加速轉移
1149 - String Removals
題解
定義 代表考慮前 個字元,以第 個字元為結尾的 subsequence 數量
則轉移式如下:
若在位置 是第一次出現該字元,則
否則若前一次是在第 項出現該字元,則
原因的話是之前的字元都會在第 項時算過,這樣就重複計算到了
這樣可以用前綴和在 時間轉移,總複雜度
1188 - Bit Inversions
題解
開兩棵可以求出區間最大連續和的線段樹,詳細可以看 CF EDU 的教學
一棵將 1 的位置設為 1 ,0 的位置設為
另一開將 0 的位置設為 1 ,1 的位置設為
之後每次修該完就能直接求答案
有另一種較狗幹的 set 實作法,因為我討厭那種作法就不提了 =w=
2419 - Xor Pyramid
題解
根據巴斯卡三角形,第 層第 個位置會被算到的次數為
而原問題是 xor,所以只要能就快速判斷 的奇偶性就好
方法是可以先預處理出 的質因數分解 2 的次方是什麼,然後就能在 的時間完成判斷
1086 - Writing Numbers
1113 - String Transform
題解
連結
我就懶
2427 - Letter Pair Move Game
題解
首先 直接暴力判所有 case,或可以用簡單的爆搜解決。剩下考慮 ,一開始可以先用數次操作將空格移到最右邊兩格。
考慮每次操作先找到最小的 滿足 是 B,再找最小的 滿足 是 A 且 ,則分一下 case(0-based)。
進行操作
進行操作 後,只會剩下兩種可能:
- 條件達成
- 變成
原因:首先若 是 B,那 後面只會剩下一個 A,做完上述操作後確實會結束
而若 是 A,唯一可能的字串為 這種形式,而這種形式做完之後就會變成上述 2. 的字串。
而若變成 2. 的字串,再做 後會變成 。
- 不存在
字串一定會是 ,做 即結束。
對於 的情況,只會用 3 次操作,且操作完後 會變成 A。而剩下兩個情況頂多用 7 次操作就可以達成條件,所以可以發現最多只要 3n + 7 次,就算加上最開始搬空格操作依然是綽綽有餘。
1147 - Maximum Building I
題解
對於每一個位置,存下他往上撞到障礙物之前最高的高度,之後就用跟 Advertisement 一樣的做法解決
變成需要做 次複雜度為 的單調對列
1162 - Sorting Methods
題解
- 答案是 inversion 數量,即為 的對數
- 對於每一個 ,建一條 指向 的有向邊,則答案為 環的數量
- 答案是 lis 長度
- 定義 為最小的數 s.t. 為原陣列的 subsequence,則答案為
說明
-
首先,每次 swap 相鄰字元最多只會讓 inversion 數量減 1,所以交換次數至少為 inversion 數量
接著講一個顯然結論:若一個陣列滿足 ,則 inversion 為 0
這可以得出當 inversion 不為 0 時,必可以找到一個 index s.t.
也因此,無論如何一定可以交換兩相鄰數使 inversion 數 -1
因此證畢,最少次數為 inversion -
首先,用前面建邊的方式建圖,那最終狀態會是環數量為 ,而每次 swap 操作後最多只會讓環的數量 +1
接著當環不到 時,根據鴿籠原理必存在一個環大小 ,又挑選同一個環上的任意兩點都可讓環的數量 +1
因此最少次數就是 環的數量 -
顯然發現可以花一步將任意一個數移到正確的位置,因此目標可以視為最大化不移動的數字數量
而這可以發現 lis 就是所求 -
同上,同樣思考如何最大化不移動的數字數量即可推出結論
1191 - Cyclic Array
題解
做法 1
我覺得這蠻玄的 XD 不過非常好寫
以下定義 為從 開始的最遠的值使的區間 的區間和 ,則:
1 | int now = 0; |
然後就結束了 =w=
計算 last 的方式可以用雙指標做完,複雜度
做法 2
用倍增的方式,定義 為從 開始,跳 段的和
之後就可以窮舉起點,每次從最高的 bit 開始跳
若跳完總和不會超過就把答案加上去
而每個點最多跳 次,因此總複雜度
2414 - List of Sums
題解
先把 陣列排序好
首先,只要我們知道了 ,那麼就可以還原出整個陣列了
(每次挑選出 陣列的最小值,把他和 相減即可)
接下來,我們會知道
(原因:)
又由於 的值只有可能在 到 其中之一
所以可以推出只有 種可能的 值,就每一個都試一遍即可
2132 - Increasing Array II
題解
1189 - Food Division
題解
首先定義 ,每次操作同於挑選兩相鄰數,或是第 項與第 1 項一個 +1 一個 -1
而換個角度想,當 建好前綴和 ,那每次操作相鄰同於將一個數字 +1 或是 -1
至於挑選第 項與第 項做操作,這樣同於 都 +1 或 -1
有了這些結論,可以說明答案為以下式子最小的可能值:
而顯然 為 與一個 0 所形成的陣列的中位數時該值最小
1654 - Bit Problem
題解
1698 - Swap Round Sorting
題解
首先先建邊 並拆成好幾個環,首先會有以下顯然結論:
- 若所有環的大小都為 1,那答案為 0 次操作
- 若所有環的大小不是 1 就是 2,那答案為 1 次操作
否則考慮一個大小為 的環 ,先做以下操作:
- 是奇數:
先交換 與 ,接著交換 與 、 與 … - 是偶數:
先交換 與 ,接著交換 與 、 與 …
之後可以發現每個環都變成大小至多為 2 的環,因此再一次操作即可,總共 2 次
2430 - Binary Subsequences
題解
首先,用一個跟之前這題作法不同的角度來想一下
定義 為考慮前 個字元,結尾為 0 的 distinct subsequence 數, 則代表結尾為 1 的
轉移式的話首先若當前結尾字元為 ,那 ~ 的維度一定不會變,而 的維度呢?
顯然的結論是每一個以 結尾的 subsequence 都可以轉換成結束在第 個位置,那就可以推出轉移式:
base case: ,最後答案是
再來需要一些觀察:
- 對於任意的 01 字串,他所產生的 pair 是唯一的
- 給定兩數 ,則可還原出唯一的 01 字串使的 ,或是判斷出無解
後者的方式是不斷將較大數減掉較小數 + 1,過程中若兩數相等且不為 0 則無解
當兩數皆為 0 就代表結束 (已回到 base case)
而我們需要找的是最小的 01 字串長度使的 為給定的數字
所以可以窮舉每一個 的值,使用類似輾轉相除法的方式把複雜度壓到一次 ,最後再還原出字串即可
1700 - Tree Isomorphism I
題解
我們嘗試將有根樹標上編號,編號相同則代表兩者同構,否則代表不同構。注意到兩棵有根樹同構的條件為:
- 將兩棵樹根的兒子數量相同,且樹根兒子的子樹們可以一一對應,對應的兩者為同構關係
所以如果我們得知了一棵樹拔掉根後每一個連通塊的編號,那就只要檢查他們的集合是否相同即可。
因此我們開一個 map<vector <int>, int>
,能夠將樹根兒子的子樹編號們打到一個這棵樹的編號,之後就只要 dfs 一遍沿途維護好每棵子樹的編號即可。
如果有點難理解的話可以參考一下別人的 code
2228 - Counting Sequences
題解
以 為例,答案等於:
(所有可能) - (沒有 1) - (沒有 2) - (沒有 3) + (沒有 1、2) + (沒有 1、3) + (沒有 2、3)
而沒有的種類數相同,答案數其實也是相同的,所以這樣就可以推出正確答案為:
而這個算式其實就是 Stirling numbers of the second kind
1703 - Critical Cities
1706 - School Excursion
題解
首先,這題先用並查集可以轉換成這個題目:
有 個東西,每一個有重量 ,且
,請輸出有沒有可能湊出重量
這個問題很明顯是背包問題,然而 會爆欸 QQ
來點不一樣的想法吧,總和為 這個條件可以推出最多只有 種可能的重量
原因:
這樣就可以套有限背包解決這題,複雜度
痾… 這裡講一個簡單實作的方法
對於每一種重量 ,在跑 dp 的過程多開一個陣列維護若 這個重量是這一輪才跑到,那它需要用幾個
這樣要是數量太多的話就可以直接判斷出來
我還是不太會表達= =,直接看 code 可能比較快
1 | vector <bool> dp(n, false); |
1709 - Coin Grid
題解
裸裸的二分圖最小點覆蓋
轉換方法是二分圖兩邊各 個點,若第 行第 列有硬幣,就把左邊第 個點和右邊第 個點連邊
而二分圖最小點覆蓋 = 二分圖最大匹配數 (可以自行 google 原因)
二分圖最大匹配可以用匈牙利解,但我是直接砸 flow la
再講一下還原解,flow 的作法是從源點開始 bfs,只走還有流的邊
最後挑選左邊還沒被跑過的點和右邊被跑過的點當作覆蓋的點即可
可以證明這是一組最小點覆蓋
1742 - Robot Path
題解
我們把移動表示成線段,我們會想要算出「最多可以移動多少,讓線段的交點數量依然為線段數量 -1」
而這個可以合理的二分搜 + 掃描線 BIT,複雜度會是兩個
這題也有很多實作細節,首先需要特判互相平行的線段是否有交點,還有相鄰操作方向相反之類的
總之,加油 =w=
2426 - Programmers and Artists
題解
首先先將人按照作為 programmer 的數值由大到小排序好,這樣會有一個性質:
若一個解是 optimal 的,假設最後面作為 programmer 的人是 ,那在 之前的人都一定有職業
證明:因為若存在一個人沒有職業,那把第 個人的 programmer 職業給那個沒職業的人一定不會變差
所以這導出一個作法:
先 ,計算出 代表從前綴 挑選 個 progammer,而其他都是 artist 的最大答案
接著 ,再計算出 代表從後綴 挑選 個 artist 的最大答案
這些數值都可以用一個 priority queue 計算完,最後再取全部最大即可
1757 - Course Schedule II
題解
作法:
每次從最大 index 且出度 0 的點開始填,大的數字開始放
證明:
假設一個點 沒有滿足條件,在上面操作被標成 ,那把他變成他可到達的最大值,假設為 ,那把所有被標成 到 的數字都 -1,這樣字典序會變更小,矛盾
而整個過程用一個 priority queue 維護出度為 0 的點即可做完
2174 - Removing Digits II
題解
首先定義 為 的答案,我們有以下觀察:
- 每次一定是移除最大的數字
同於證明
過程:考慮數歸, 時,,成立
假設 時皆成立,那麼當 時,定義 為 最大的數碼,顯然可以推論出移除 是其中一個最佳解
再令 為 最大的數碼
那麼 ,
由 跟 的關係可知 ,那麼 ,可推出
最後由數歸即證畢
- 將 變為 0 的過程中,對於任意一數 滿足 ,必存在一數 使的過程經過
有了這個有甚麼用呢?首先一個簡單的想法是每次都將十位數 -1,然而這樣顯然會超時,不太可行
那就換個角度想,考慮看看一次把一個位數 -1,那這樣就會在導出另一個問題:能不能讓十位數經由 -1 變成一個固定的數字,讓後面百位數、千位數在做運算的時候可以更方便
答案是可以的,我們將位數變成 9,這樣一來會變成需要計算的是將形式 變成 的所需步數,以及過程後個位數變成的值(即為 )
舉個例子,3234 的移動步驟為:
3234 -> 322? -> 321? -> 320? -> 319? -> 309? -> 299? -> 199? -> 99? -> 0
這部分可以先預處理, 代表考慮 個 9 與 1 個 組成的數字,其中在這些數字之前的數碼最大值為 ,需要花費的步數使的數字變成 ,則代表上述 state 變成 時的個位數字
這裡為了方便計算答案,將 的 dp 值定為讓數字變成 的次數
至於預處理的方法可以將一堆 9 才拆成頭 1 個 9 及剩下的,後半段經由前面的預處理已得出答案,所以只要將前面的字元從 9 開始扣即可
所以預處理完就順著前面的步驟移動就好,複雜度是
2180 - Coin Arrangement
題解
觀察:存在一組最佳解使的對於任意兩相鄰 column,不會同時往左移動又往右移動
原因是這樣可以把他轉成往上動往下動
這也導出一個 greedy 策略:首先為了方便先將每一個數字 -1,問題變成要讓所有數字變成 0
之後從左邊掃到右邊,維護現在上排和下排的數字,如果上下正負號不一樣就移動直到兩個變一樣
跑完最後即為最佳解
2176 - Counting Bishops
題解
(from dannyboy)
首先先將座標轉 45 度
可以發現白色和黑色互不影響,可以分開考慮
接著我們要對於每一個 ,算出在每一行、每一列最多只能放一個棋子的情況下,有幾種放法可以放下 個棋子
而這要怎麼做呢?我們改變一下行的順序吧,把他排成如下圖這樣:
因為每個 row 可以放棋子的部份都會被後面的 row 覆蓋,就可以好好 dp 了。考慮 代表前 行放置 個棋子的方法數
轉移式:
其中 為第 行的格子數量
而兩個棋盤分別算好 dp 之後就能直接用卷積的式子算出答案了
2432 - Grid Puzzle I
題解
flow 經典題,建模方法為:
左邊 個點,右邊 個點
將 source 到左邊第 個點連一條流量為 的邊
將右邊第 個點到 sink 連一條流量為 的邊
之後中間全點對連一條流量 1 的邊
然後直接讓他流下去即可
2131 - Grid Puzzle II
題解
同上,只是改為 MCMF 即可
實作上可以將邊的 cost 定為負數,這樣跑出來的就會是 max value
1080 - Empty String
題解
代表將區間 變成空的方法數
那轉移的話可以考慮枚舉第 個字元會和誰消除掉,轉移式為:
其中轉移式包含 combination 的原因是要考慮兩個區間誰先取後取的方法數
1078 - Grid Paths
題解
首先,先解決障礙數為 0 的問題:在一個 的矩形,每次移動只能往右或往下走,要從左上角走到右下角的方法數是多少?
因為往左走的次數會是 ,往下走的次數會是 ,所以方法數是
那接著來考慮 dp 吧, 代表不經過其他障礙物,最後停在第 個障礙物位置的方法數
轉移的直觀想法是用扣的,把方法數扣掉已經先經過其他點的情況,而具體上的想法可以考慮下面兩種 case:
1.
1 | s.k. |
為任意兩障礙物, 為起始點
可以明顯發現 -> -> 的路徑與 -> -> 的路徑不會重複
2.
1 | sk.. |
由於經由 dp 算出的 -> 路徑不會經過 (由定義)
因此可以發現 -> -> 的路徑與 -> -> 的路徑一樣不會重複
也因此,對於任意兩個在 點左上方的障礙物,所有經由他們到達 的路徑都是相異的
對於 來說,需要扣除的答案其實就是:
而計算 dp 可以先將點座標按照 、 由小到大排序,即可按照順序 dp
有一個小 trick 是可以加一個點座標為 的障礙物,而答案就會是該點的 dp 值
總複雜度
2115 - Bit Substrings
題解
假設蓋好前綴和 代表前 項的 1 數量
再算好 代表 的數量
那麼題目變成要對於每一個 ,計算
而這個是一個卷積的形式,可以用 FFT 來做完,詳細作法是將一個正的 多項式和一個負的 多項式乘起來
2075 - Reversal Sorting
題解
反正操作可以做到 次,那就每次都讓一個人回到正確的位置即可
那作法就是需要支援快速區間 reverse 和詢問最小值
那這些用一棵 Treap 即可解決,只要維護最小值即可
2421 - Counting Reorders
題解
考慮排容,令 為一個長度為 的 01 字串,則定義 為滿足 的字串數量
又定義 , 為 字元 1 的數量
那答案會是
換個角度想,我們想要將一個字串分成許多個區間,區間內的每個字元都相同,而且我們事實上只在乎有幾個區間而已,因為只要有區間數量就可以知道排容正負號了
因此我們可以考慮 dp 了, 代表考慮前 種字元,已經分成了 個區間
那考慮主動向上更新,枚舉第 種字元要被分成 個區間,則可以得出轉移式:,其中 為第 種字元的數量
第一個 C 為考慮原本區間與當前區間的排列數,第二個 C 為考慮將第 種字元分成 個區間的方法數
乍看之下複雜度是 ,但實際上如果每次 dp 第二個維度只跑到當前可能的最大上限,複雜度就會變成好好的
原因的話可以使用樹背包的想法,因為我有點懶就交給讀者自己思考了 (X
1159 - Book Shop II
題解
有限背包,這裡來講一下單調隊列的作法
為考慮前 個物品且重量為 的方法數,則
變一下
再變一下
這樣就可以分開處理每一個同餘的數字,而轉移的又是一段遞增區間的最大值
可以使用單調隊列優化讓複雜降低到
1677 - Network Breakdown
題解
如果只有加邊的話用並查集可以輕鬆維護,刪邊的話好像不太能直接做
那要是將詢問反過來做的話呢?這樣一來刪邊就變成加邊了,也就可以用並查集直接做完了
這個技巧也叫做時光逆流
1203 - Visiting Cities
題解
對正圖以 1 為 source 做一遍 dijkstra,並維護到每一個點的最短路徑數
再對反圖以 為源點做一樣的事
而判斷一條點是否為答案的條件就是距離和 = 原本的距離且方法數乘積 = 原本的總方法數
而要注意的是存下的方法數可能會太大,所以要取模
也因為這樣,這個方法有一定的錯誤率,但我也不知道其他作法就是了 owo
2184 - Missing Coin Sum Queries
題解
首先先講一下 的作法(就是這題)
方法是將硬幣由小到大排好,一開始 ,每次跑到一個價值硬幣 的時,要是 ,就代表 無法被湊出來,因此輸出答案
否則代表到 都可以湊出來,因此把硬幣加上去,繼續跑下去
要推廣到多筆詢問的話,首先觀察到要是當前的最小值是 ,且符合 ,那其實介於 到 的數字都可以直接加進去
這樣就可以導出一個做法:
- 將所有硬幣分成 30 層,第 層存下價值介於 的硬幣
- 從第 0 層開始跑,若該層有硬幣,就取出該層的最小值
若滿足 就保證該層硬幣都可以直接加進答案
因此只需要支援靜態詢問區間最小值與詢問區間和即可
複雜度為
然而這樣可能會被卡常,可以離線用並查集做 RMQ,複雜度可以壓到
1157 - Number Grid
題解
打表一下
1 | 0 1 2 3 4 5 6 7 |
然後就出來了
1148 - Maximum Building II
題解
參考類似 Maximum Building I 的做法
這裡在蓋 陣列時蓋成非嚴格,即為: 代表最小的index s.t.
這樣一來可以發現,我們會有 種不同的矩形,如下圖
而為了要快速計算每一種的形狀,可以採取以下方法:
-
首先,先推出給定 ,寬為 的數量為:(WLOG, )
, if
, if
, if -
可以發現這是線性函數,可以用以下方法:
寬度為 的數量,我們使用兩個數值 表示答案為
這樣一來,要將答案加上 的貢獻可轉換成:
- 區間 值 +1
- 區間 值 +
- 區間 值 -1, 值 +
這樣一來就轉換為一堆二維的矩陣加值問題,用二維前綴和先預處理完即可
2423 - Filling Trominos
題解
首先, 一定要有一個是 3 的倍數,WLOG 假設 是 3 的倍數
接下來狗幹分 case:
- 或 ,顯然無解
- 偶數,那就拆成一堆 的矩形即可
1 | AACCEE |
- 偶數、 奇數,可以拆成前 3 行與剩下,前 3 行可以拆成 的矩形,剩下的回到上面的 case,拆成一堆 即可
1 | AABAA |
- 其他( 皆為奇數),首先 或 顯然無解
其他的話由於 有解:
1 | AACAA |
所以可以將原棋盤拆成 、 與 ,用前面的 case 即可解決
這裡再講一下塗色的方法,可以觀察到:
1 | AA.AA |
無論 T 型怎麼擺都不會撞到這兩塊,因此將 T 型左上角的 row 與 col 座標模 3 後即可直接用這個做為顏色
1161 - Stick Divisions
1665 - Coding Company
題解
首先先把值大到小排序好
這樣一來每一組值的計算方式可以變成在新建組的時候將值加上去,結束組的時候將值扣掉
接著考慮 dp, 代表考慮前 個人,現在有 組尚未結束且當前的值是
轉移用主動向上轉移,case 有:
- 第 個人新建一組,state 會從 變成
- 第 個人加入其他的組,但沒有結束,會有 種可能,state 會從 變成
- 第 個人結束其他的組,會有 種可能,state 會從 變成
最後答案是
1699 - Flight Route Requests
題解
首先,可以只花 條邊將 的點強連通:
做法:
考慮有向圖轉無向圖的每一個 CC,令 為 CC 的點數,若這個 CC 的有向圖有環,那就代表邊數一定 ,花 條邊即可
否則代表那張圖是 DAG,只需要花 條邊
最後把每個 CC 答案加起來即可
2402 - Two Stacks Sorting
題解
首先觀察一下發現兩個數 如果滿足以下兩個條件,那它們必須要放在不同堆:
- 存在 滿足
而只要有一項沒滿足的話,它們兩個放在同一堆也沒差,所以這可以得出一個 的解:建完邊後判斷圖是不是二分圖即可。
優化方法的話,首先注意到我們只要找到這張圖的隨便一棵生成森林,二分圖著色後再 檢查一遍這個擺法是不是好的就好了。
再來我們要想辦法化簡有邊的條件。假設 時,條件為 ,其中 為 的最小 。
這個條件已經足夠簡單了,我們就可以來優化前面的二分圖塗色:建一棵區間詢問最小值位置的線段樹,每次跑到點 時,就暴力在 這段區間內尋找符合條件的 ,每次找出最小的只要檢查是否 就可以繼續拜訪下一個點了。而只要再記得將跑過的點從線段樹上刪掉,複雜度均攤就會是好好的 。
而 的邊可以用類似的方法處理,因此整體二分圖塗色就可以在 做完。
1701 - Tree Isomorphism II
題解
跟 Tree Isomorphism I 相比,現在變無根樹了
可以發現兩棵樹若是同構的,樹重心(們)也一定會相同
所以就以樹重心為根做跟上一題相同的事就好
1705 - Forbidden Cities
題解
首先先對這張圖點雙連通分量縮點,如果 不是割點的話那顯然答案是 YES
否則要是縮點後 點在 和 的路徑上,就代表答案是 NO,否則為 YES
而後者要判斷的話可以用樹剖將路徑拆成一堆區間,再判斷那些區間有沒有包含 即可
複雜度會是一個 ,因為只需要判斷區間而不用線段樹
1741 - Area of Rectangles
題解
經典題,叫矩形覆蓋面積(?
做法是使用掃描線讓問題降低一個維度,變成需要支援:
區間加值、減值
詢問整體區間非 0 的數量
而注意到這整個過程中不會有負數,所以開一棵線段樹維護最小值與最小值數量即可
2429 - Grid Completion
題解
一臉組合題,首先可以先把問題轉換成這樣:
給定兩個長度 的 array ,陣列每一項可能是 -1 或是 1 到 n 的整數
現在請將每個 -1 填上一個 1 到 n 的整數,使:
- 都是 permutation
考慮排容,令 為一個長度為 的 01 字串,則定義 為滿足 的字串數量
又定義 , 為 字元 1 的數量
那答案會是
接著思考甚麼情況會 ,有以下三種可能
- 且
- 且 ,數字 並未在出現在原始的 陣列
- 且 ,數字 並未在出現在原始的 陣列
因此方法就出來了,首先先計算出三種可能的位置數量,假設位置數量分別為
再計算出有多少個數字沒有出現在兩個原始陣列中,假設此數量為
再計算出有多陣列 分別有幾個 -1,假設此數量為
由於每個位置最多只會被分在一類,因此答案為:
1752 - Creating Offices
題解
結論:隨便取一個點當根做 dfs,將點按照深度大到小開始看,若可以選的話就選,不能選就跳過,可以證明這樣出來會是其中一組最優解
證明:(from syl123456)
只要證明選取一些點之後,以下兩件事情會發生至少一件即可:
- 深度最深的點可以被選
- 將其中一個已被選的點換成深度最深的點仍然一組最優解
令最深的點為 ,當 1. 不成立時,必存在一點 使的他已被選取且
令 ,則選擇 會擋住的點包括:
- 的子樹 (原因: 為深度最深的點)
- 在 子樹外且滿足與 距離 的點
考慮在 子樹以外的點,因為 是深度最深的,所以將 換成 不會讓子樹以外被擋住的點數變多,也因此換完仍然會是一組最優解
由上可證明原本的 greedy 策略,只要不斷取深度最深的點即可
至於檢查某點使否可被選擇可以用重心剖分維護距離最小值,詳細可以參考 CF 這題
複雜度是常數有點肥的
1075 - Permutations II
題解
首先,這題 OEIS 搜的到,而且複雜度可以做到 ,但我覺得這題的 想法很優質,所以還是講一下
我們考慮 dp: 代表考慮長度為 的 permutation,有 對相鄰數字皆 且相差 1
其中 代表 與 相鄰, 代表無
轉移用主動向上轉移,case 有:
- 將 插在 旁邊
state 從 變成 ,這有兩種可能 (左右)
state 從 變成 或是 - 將 插在原本相差 1 的數之間
state 從 變成 ,這有 種可能 - 其他
state 從 變成 ,這有 種可能
而最後答案會是
2415 - Functional Graph Distribution
題解
首先,這題 OEIS 還是搜的到,但還是講一下正常的作法,先在這裡感謝 dreamoon 的提點 > <
先講個東西叫 Cayley’s formula,這東西說明了 個有編號節點所能形成的無根樹數量為 ,詳細可以使用前面提過的 Prüfer Code 來證明,有興趣可以自行研究或查詢
而這東西是有 general 版的,大致如下:
「滿足以下的無根森林數量:
- 有 個有編號的節點
- 有 個連通塊
- 點 在不同連通塊
為 」
詳細證明可以使用數學歸納法
那我們再回來看這題,首先,先說明 要如何計算
因為 functional graph 的一個連通塊一定會有一個環,所以考慮枚舉「環的大小」吧
當環的大小為 時,我們有 種挑選環上點的方法,而環中又有 種排列方式
現在剩下環外的邊了,考慮把環的邊都拔掉,會發現題目問的就是上面 Cayley’s formula general 的版本
所以方法數會是
整體而言,答案會是:
現在來考慮 general 的 case,我們稍微改一下枚舉的東西,改成枚舉「 個連通塊的環的大小總和」
假設數量是 ,這樣的話一樣有 種挑選環上點的方法,接著要把 個點拆成 個環,這個東西的方法數其實就是 stirling number of first kinds。這有個簡單的遞迴式:
,其中
最後環外的邊一樣把環的邊拔掉後用 Cayley’s formula 解決掉,整體答案會是:
再經過整理即可得出與 OEIS 相同的式子
可以使用 建出 stirling number,再用 算答案即可
1685 - New Flight Routes
題解
首先,我們先將圖 SCC 縮點,會變成一個 DAG,而問題就是最少需要加幾條邊才能讓這張圖變成只有一個 SCC
假設那張 DAG 有 個 source, 個 sink,(source = 入度 0 的點,sink = 出度 0 的點)
那答案顯然會有下界 ,那我們會合理懷疑這就是答案
這時我們可能會想說亂連會不會就對了,然而這是有反例的:
如果連 2->1 和 3->4,那會是好的,然而,要是不小心連到 2->4 和 3->1 就慘了
總之,數量確實是這樣沒錯,問題就是到底要怎麼連才會是好的
首先,建一張二分圖,左邊為所有的 source,右邊為所有的 sink,兩個點 之間有邊 iff 可以經由一些邊走到
那,我們先找出這張二分圖的 maximal matching,假設 matching 為 ,那就先建邊
注意的是這裡是 maximal matching,代表需要移除至少一條匹配邊才能讓一條非匹配邊加入
至於剩下沒匹配到的點呢?這些點亂連就好了
具體作法是每次挑出一個 source 和一個 sink,連 sink 到 source 的邊
至於還有多出來的點的話就亂連即可
Proof: 考慮這種連邊情況下的每一個 sink,要證明他們都能走到每一個 source
首先任意一個 sink 都可以走到至少一個 source
接著由於這是 maximal matching,任意一個 source 都可以走到一個 matching 上的 sink
再來由於這是 maximal matching,matching 上的 source 能走到所有的 sink
所以證完了
最後是實作的問題,因為這張新圖邊的量級是 我們不可能建出所有的邊下去找 maximal matching
這裡我是直接用 flow 跑增廣路的方法下去硬找
複雜度是玄學,但總之因為 flow 很快所以這樣不會 TLE
其他做法的話可以在 dfs 的過程中,將已經跑完的邊用並查集壓縮起來,這樣只要一只對 source dfs 就好
2418 - Grid Path Construction
題解
可以參考一下 Hamilton Paths in Grid Graphs 這篇論文,稍微看一下這篇可以激發不少靈感 XD。
首先不失一般性假設 且令起點終點為 ,同時假設起點為較左邊的點。另外,定義左上角的格子為 ,右下角為 ,若 為偶數則格子 為白色,否則為黑色。除了顯然的塗色判無解方法外,論文的 3.1 告訴我們還有以下幾種無解情況:
- 且起終點不是 與
- 且 為偶數、 為黑色。同時以下兩種條件至少一項成立:
- 與
那看完論文的 3.2 後我們可以得出以下作法,同樣不失一般性假設 且起點為左邊的點:
- :直接用爆搜找答案
- 且 :切成左邊 與右邊 ,之後做以下事:
- 遞迴找到右邊長方形的解
- 上述解中一定可以找到在最左邊直排往上或往下的一步,將它改成往左與往右,最後在這兩步之間插入對應的左邊長方形的解即可。討論一下 的 case 後可以發現兩塊長方形一定都可以找的到解。
- 且 :左右相反後變成上面那種 case
- 其他:一樣切成左邊 與右邊 兩塊,之後做以下事:
- 找到一個點 使存在一組 的長方形從 到 的解
- 遞迴找 到 在 長方形的解與 到 在 長方形的解後接起來即可。同樣討論一下 的 case 可以證明右邊長方形一定找的到解。
剩下就只剩實作問題了,個人覺得這個作法已經足夠乾淨了,有想清楚就不會到很難寫。