Problem
給定三個曲線,每個曲線通過 n 個點,分別表示 r, g, b 的轉換圖示,請將影像的 rgb 替換成曲線給定的結果。
特別注意到曲線的最左、最右端點之外都是水平線。
Sample Input
|
|
Sample Output
|
|
Solution
處理最煩的就是通過 n 個點的曲線怎麼求,套用高斯消去法,假設多項式有 n 個係數求解。
|
|
給定三個曲線,每個曲線通過 n 個點,分別表示 r, g, b 的轉換圖示,請將影像的 rgb 替換成曲線給定的結果。
特別注意到曲線的最左、最右端點之外都是水平線。
|
|
|
|
處理最煩的就是通過 n 個點的曲線怎麼求,套用高斯消去法,假設多項式有 n 個係數求解。
|
|
給斐波那契数十進制的最後 $K$ 位$F_n$,反求該數字符合的最小項 $n$ 為何。
|
|
|
|
前續 b443: 我愛 Fibonacci 的解法,得到 $\mod 10^K$ 的週期長度,分別求出 $K = 1, \; 2, \; 3, \cdots 17$,採用 b443 的作法,可以在 7 秒左右完成週期計算。
也可以選擇本地建表,得到週期長度分別為以下,直接打表使用。
|
|
當要求尾 K 位的結果,要從尾 1 位的符合位置開始窮舉,隨後增加一個週期。例如現在已知尾 i 位符合的項數位置 `pos
,那麼在位置 pos + C[i]
檢查是否有符合,並且滿足 pos + C[i] <= C[i+1]
,之後就是循環週期不必窮舉。
由於週期之間的大小倍率不大,窮舉情況非常少,大約可以在數萬次內解決,為了加速數列計算,矩陣乘法需要 $O(\log n)$ 的時間,可以預處理相乘的矩陣,由於每一步跨越的項數是固定的,那麼計算量就會降到 $O(1)$。
關於模乘法
|
|
由於 long double
有效位數 106-bits,用在 64-bits 模乘法非常夠用,取代模擬的加法計算,速度快個四到五倍。
|
|
給一個彩色影像上面有數個相同大小的硬幣,硬幣之間不會重疊,但會有部分碰觸,請問影像中有多少個硬幣。
|
|
|
|
若圓形彼此之間不相交,可以用二值化 + 灌水法 (flood fill) + 分團大小檢測。現在圓形有相連可能,出題者給我附前測代碼啊,咱們兩個比一下速度 … 總之步驟是
關於霍夫轉換,其中$x_c, \; y_c$ 表示推向圓心的座標,而 $r$ 表示窮舉的圓半徑,$gx$ 表示 x 方向的 sobel 差分,同理 $gy$,而 $g = \sqrt{gx^2 + gy^2}$。
$x_c = x - r \times (gx / g)$ $y_c = y - r \times (gy / g)$
當然閥值判定都還不到位,手動測試好幾個版本,OpenCV 的寫法值得去 trace 一下。代碼僅供玩玩,不代表在其他情況也能使用。
|
|
進行圖片變形,效果類似哈哈鏡的作用。
對圖片水平中線進行座標 y' = sqrt(y)
變換,將靠近中線的像素盡可能地拉往中間,形成延伸的變形效果。
|
|
|
|
計算公式
(H/2) - pow(i-H/2, 2)/ (H/2)
pow(i-H/2, 2)/ (H/2) + H/2
分別套用後,逆轉換到原圖上,進行最近鄰居查找,。
|
|
題目連結,加速以下的程序計算,下方程式需要 $O(N^3)$,若用前綴維護總和也需要 $O(N^2)$。
|
|
最後輸出所有 ret[sum]
的對應結果。
|
|
|
|
由於是一個很樸素的計算,為了加速運算不套點資料結構和算法是不行的。看到對於每一個結果都要輸出,因此可以想到快速傅立葉 FFT 的旋積計算,接下來就要思考如何構造多項式 (向量)。
假設前 $i$ 個數字的前綴和$s_i$,為了要計數反應在係數,而索引值要反應在項數,因此得到兩個 $x$ 多項式相乘,若要統計區間 $[l, r]$ 的總和,則反應在$(i - j) x^{s_i} \times x^{- s_j} = (i-j) x^{s_i - s_j}$。但這樣的計算無法一次完成,因此要拆成兩次計算,分別得到$i x^{s_i - s_j}$ 和$-j x^{s_i - s_j}$。
明顯地前者構造$(\sum i x^s_i) \times (\sum x^{-s_j})$,後者構造$(\sum x^s_i) \times (\sum -j x^{-s_j})$,利用快速傅立葉 $O(n \log n)$ 計算多項式相乘,隨後相扣即可。
特別注意到總和 0 要特別判斷,因為構造法無法計算。此外這題非常講究精準度,可以利用 NTT/FNT 全部都在整數運算,又或者使用 FFT 在 double
形態下完成,特別小心 FFT 通常會利用角度疊加 (合角公式) 來加速運算,但不幸地這裡會遇到精準度誤差,必須採用 cos, sin 全建表。其他人容易遇到要用 long double
取代 double
計算是因為這種寫法的問題。
|
|
|
|
|
|
模擬計算,刪除組合數字的倍數,請問剩下多少個數字可選。
|
|
|
|
這一題是非常容易的題目,利用排容原理可以在 $O(2^m)$ 的時間內完成,所以複雜度沒有太大的改善,若使用 bitmask 的方式撰寫,複雜度會落在 $O(m \times 2^m)$,中間會有大量的 gcd(a, b)
計算,歐基里德輾轉相除法的常數並不大,時間複雜度 $O(\log n)$。
為了加速運算,可以利用組合來完成,利用選用組合 1011
,可以得到 lcm(1011) = lcm(lcm(1010), A[0])
完成,因此只會有 $O(2^m)$ 次 gcd()
的成本,整整少了常數 m。
因此需要使用 lowbit = i&(-i)
的位元運算技巧,同時為了得到 2 的冪次的次方數,建議使用內置函數 __builtin
系列,編譯器最佳化計算。而 gcd 使用,也使用內建的 __gcd(a, b)
但內置函數通常只會設置在 unsigned int
意即 32-bits 無號整數,要防止運算 overflow。
有人會提案使用建表,這樣查找只需要 $O(1)$,但記憶體置換會造成負擔,有兩個 $O(2^{15})$ 的 cache page,而且還要前置建表的計算成本。根據實驗測試,建表的方案速度會比較慢。
|
|
給一張 $45 \times 45$ 的經典 Lena 彩色影像,共有 $2025$ 個像素,程式碼長度上限 10K,避開打表的長度限制,輸出一模一樣的圖形。
|
|
|
|
由 Zerojudge b454. 請輸出這張圖片的 RGB 數值 改編,強迫使用 base64 的方案,使用一般的 16 進制輸出編碼會超過限制。16 進制下,共計需要 $6075$ 個 0 - 255
的整數,共計需要用 $12150$ 個可視字元。
根據前一題的實驗,雖然霍夫曼編碼會比較短,但是還要附加解壓縮的代碼一起上傳,除非寫短碼否則很容易虧本。而 lz77 是不錯的壓縮方案,但用在影像中很容易虧本,因為重複的片段並不高。因此最後選擇直接使用 base64,則可視字元數量可以降到 10K 以下,接下來就比誰的短碼能力好。
base64 只用到 0-9a-zA-Z+/=
這 64 個字元,為了在一般 C/C++
的字串宣告語法,跳逸字元如 \\
、\t
、\n
… 等必須用兩個字元表示,通常都是在 ASCII [0, 31]
為跳逸字元。蔡神 asas 直接用連續片段,因此會有一些跳逸字元,儘管跳逸字元占用兩個以上的字符,會多好幾個字元,但就不必編寫解碼程序,不用去寫繁複的映射,代碼居然比較短。
|
|
|
|
利用巨集展開重複的指令
|
|
給一張圖,給定起點 S、終點 T,把任意邊的權重放大兩倍,請問最多能拉長最短道路長度為何。
意即修改一條邊找替代道路,使得替代道路越長越好。
|
|
|
|
終於向 liouzhou101 問到解法,類似 BZOJ 2725 Violet 6 故乡的梦,過了三年多,才把 Angel Beats 加強版寫出來,終於完成了,代碼是如此地迷人,一堆掃描線算法,找瓶頸也用掃描線代替感覺萌萌哒。
移除掉一條邊,找到 S-T 最短路徑的替代道路 $O(E \log E)$ 離線處理。
multiset
,維護替代道路的可行性,保持掃描線左右兩方各自通過的瓶頸數接起來不會通過瓶頸。
|
|
變數說明
Ds(u)
表示從起點 S 到 u 的最短路徑De(u)
表示從 u 到終點 T 的最短路徑Bs(u)
表示從起點 S 到 u 保持在最短路徑上,經過最少的瓶頸數Be(u)
表示從 u 到終點 T 保持在最短路徑上,經過最少的瓶頸數例如先找到 shortest-path DAG 圖如上,則 D - G
和 J - T
都是瓶頸。瓶頸有兩種找法,第一種是類似最大流的最小割,第二種是轉換成區間,例如 e(u, v)
則可以得到區間 [Ds(u), Ds(v)]
,然後做掃描線算法,得到哪個時候只有一個區間,則那個邊就是瓶頸。
得到所有瓶頸後,依序掃描離 S 近到遠的瓶頸,維護一個 mutliset<int>
,當窮舉瓶頸 e(u, v)
,則替代道路為 min(Ds(a) + w(a, b) + De(b))
,滿足 Bs(a) <= Bs(u)
和 Be(b) <= Be(v)
。條件 Bs(a) <= Bs(u)
和 Be(b) <= Be(v)
是為了保證不經過這個瓶頸。
隨著掃描線移動,Be(b)
遞減,Bs(a)
遞增,因此先排序 Bs[]
和 Be[]
,每當掃描到一個瓶頸,鬆弛新的路徑,同時移除掉最小堆中 Ds(a) + w(a, b) + De(b)
且 Be(b) > Be(v)
的所有元素。
英文名稱 The selfish-edges Shortest-Path problem
|
|
給一張 $64 \times 64$ 的經典 Lena 影像,共有 $4096$ 個像素,程式碼長度上限 10K,避開打表的長度限制,輸出一模一樣的圖形。
|
|
|
|
首先遇到的問題是將灰階圖片取出,變成一般常看到的數字格式,直接用 python 來完成,必要時需要安裝 install Python Imaging Library PIL,這是前處理,也可以用其他的 OpenCV 來完成。
|
|
|
|
由於 4096 個像素,若使用十六進制表示每一個 0 - 255
像素,只需要 2 個字元 00 - FF
,程式碼長度大約會落在 8192 bytes,若使用十進制,需要使用 3 個字元表示,那麼大約是在 13KB,所以至少要用十六進制表示法。
還有更好的方式,如一般常在網頁上瀏覽的 base64,它充分利用 64 個可視字元進行編碼,相較於十六進制只使用 16 個可視字元來比較會更加地短。利用傳統的霍夫曼編碼 (huffman coding) 來壓縮,結果會短個 10% 到 20%,當每種像素次數分布相當懸殊時,效果就越好,但上傳時還要附加解壓縮的代碼,所以沒辦法短太多,還不如直接 base64。由於是圖片,漸層效果比較普遍,改用與前一個相鄰像素的差值來轉換,得到的分布會比較極端,帶入 huffman 的效果就會不錯。
最後,還有一種比較容易實作的 lz77 壓縮,比較類似最長平台,每一次的訊息為 (起始位置, 重疊長度, 補尾字元)
,設定一個 window 長度,然後 sliding 滑動,但是起始位置、長度需要選好,代碼中嘗試用 window size = 16
,則起始位置和重疊長度可以用 8-bits 表示,越大不見得越好,太小也不是好事,但不管怎麼做,由於影像的重複 pattern 並不多,沒有像一般數學性質的數列來得強,壓縮實驗不管怎樣都大於 10KB。
關於實作細節,霍夫曼編碼儲存格式是 壓縮位元長度 bits length + 字典表 + 壓縮資料
,對於字典表的儲存有很多種,由於是 complete binary tree (只會有兩個、零個子節點),可以用一個 0 / 1 前序走訪來完成,這會造成解壓縮代碼長度就會有點虧本,所以在代碼中直接使用一般的打表,所以要保證每一個壓縮完的最大 bit-length 小於 32 來方便型態 int32 操作。
方法 | 代碼長度 (bytes) |
---|---|
base64 | 6485 |
huffman+base64 | 7965 |
diff+huffman+base64 | 7717 |
lz77+base64 | > 10K |
|
|
|
|
|
|
|
|
圖片匹配和字串匹配有一點不同,字串匹配通常要求其子字串與搜尋字串完全相符,而圖片匹配則用相似度為依據,當圖片大、複雜且具有干擾,或者需要匹配數量非常多,更先進的領域會利用特徵擷取,用機率統計的方式來篩選可能的匹配數量,篩選過後才進行圖片的細節匹配。
給予兩個圖片 $A, B$,圖片格式為灰階影像,每個像素 $\mathit{pixel}(x, y)$ 採用 8-bits 表示,範圍為 $\mathit{pixel}(x, y) \in [0, 255]$。
舉一個例子,有一個 $3 \times 3$ 的圖片 $A$ 和一個 $2 \times 2$ 的圖片 $B$,用矩陣表示如下:
$$A := \begin{bmatrix} a1 & a2 & a3 \\ a4 & a5 & a6 \\ a7 & a8 & a9 \end{bmatrix} ,\; B := \begin{bmatrix} b1 & b2\\ b3 & b4\\ \end{bmatrix}$$假設左上角座標 $(1, 1)$ 即 $a1$ 的位置、$(1, 2)$ 即 $a2$ 的位置。
比較時,整張 $B$ 都要落在 $A$ 中。現在要找到一個對齊位置 $(x, y)$,使得 $\mathit{diff}(A, B)$ 最小。
|
|
|
|
相同樸素 FFT 題目 ZOJ 1637 - Fast Image Match
雖然不是很明白 FFT,參考上面的資料,了解 FFT 的旋積 (convolution) 可以在 $O(n \log n)$ 完成就好,接著套模板。
從差異公式中得到$\mathit{diff}(A, B) = \sum (a_i - b_i)^2 = \sum a_{i}^{2} - \sum 2 a_i b_i + \sum b_{i}^{2}$$\sum a_{i}^{2}$ 和$\sum b_{i}^{2}$ 都是獨立的,麻煩的是在於$\sum 2 a_i b_i$ 向量內積,若樸素去計算會在 $O(H^4)$ 完成,套用 FFT 旋積計算,得到 $O(H^2 \log H)$。FFT 有一個缺點,在浮點數的複數域下運行,計算時會失去精準度,要四捨五入到整數。儘管如此,由於不用像 NTT 那樣有很多模運算,速度是最快的。
數論變換 (NTT) / 快速數論變換 (FNT),採用費馬數數論變換,取代複數根的疊加,利用原根的性質來完成。NTT/FNT 處理整數域內積時不存在誤差,所有計算皆在整數,但是要取模變得非常慢。
為了加速計算,丟 CRT 下去降一半的 bits,乘法速度只能提升兩倍,但要做兩次計算,速度稍微快一點點而已。在實作時,特別要注意到,CRT 運作時,挑選兩個質數 $P1, \; P2$ 分別計算 FNT,最後 CRT 逆推回去。
其他實作細節
std::complex<double>
帶 struct complex
取代,加速 method interface 拷貝。sin() cos()
不考慮建表,採用乘法和加法疊加,這會損失一點點精度。若固定測資考慮內存池去搞。在 O1 下編輯跟 O3 一樣快,由於第二點的貢獻,速度直接從快兩倍。0.3s -> 90ms。
|
|
|
|
|
|
|
|