contributed by < mesohandsome
>
藉由位元運算推算出下一輪 ,
在程式碼中,int m
對應到 ,所以每回合都除以 4,也就是右移 2 位。
int z
則是 ,除以 2 也就是右移 1 位。
使用第 2 週測驗中的 __ffs(x)
取代 __builtin_clz
。
在第 3 週測驗 1 中有一部分的敘述標示不清楚, , , 之間應有間格標示,但在 hackmd 上用 \\
換行有些情況沒辦法顯示,所以黏在一起,閱讀時在這部份產生誤解並觀察了非常久才發現問題。
為了減少乘除法運算,需要以 bitwise 運算去逼近 ,而算式必定為 , 為非負整數, 為正整數,所以找出 。
上面的運算可以看成
再把 乘以 得到接近 ,也就是 div 的結果。
也因為 ,所以將剛才整理好的 以 bitwise 操作乘以 10 倍後,與原始值相減就會得到 mod 的結果。
q
計算完會逼近 0.8 * in
,在計算 div
時要讓他接近 0.1 * in
,所以要右移 3 位。
而 mod 會等於 div 10 的結果乘 10,所以原本的 q
為 0.8 * in
並將前 3 位多餘的過濾掉,再加上 0.2 * in
( 將值為 0.1 * in
的 div
左移兩位 ) 使值變為 1.0 * in
也就是 div
的 10 倍。
使用二分搜尋的技巧,找出 為多少,因此依序填入 65536 , 256 , 16 , 2。
__builtin_clz
的輸入不能為 0
,因此先將輸入 v
和 1
做 OR 運算,避免為 0
的狀況發生。
avg->internal
表示 ,val << avg->factor
表示 ,avg->weight
表示 。
factor
用來調整可以顯示到多少的精確度。
將上面的算式化簡,就會得當與原本公式相符的結果:
讓程式一開始依據 x 是否為 0 做減法,避免 unsinged integer 減到小於 0 後出現錯誤。
最後回傳時也判斷 x 是否為 0,決定是否加 1。
以每 4 個位元為一個單位計算 1 的個數,利用以下公式計算:
將輸入 v
右移 1 位 ( 除以 2 ),然後 & 0x77777777
的用意是,如果較高位的 4 位在位移之後,右移完要移除的位元會變成低位的第 4 個位元,因此以 0x77777777
( 01110111 ... 0111
) 將這些不需要的位元清除,就可以得到每 4 個位元完整的 。
而這個操作總共用了 3 次,取出 , , 。
使用 v = (v + (v >> 4))
會得到:
再使用 0x0F0F0F0F
做 mask 得到:
與 0x01010101
相乘完後會在第 24 個位元後得到 B0 + B1 + … + B7,因此將結果右移 24 個位元即為結果。
以下程式用來找出每個數之間的 hamming distance,透過 xor 將不同的位元標示成 1,再以 popcount 找出總共有幾個 1,程式中使用了兩層迴圈來計算,因此每一組配對會被重複計算到,所以最後應該要將 total
除以 2,也就是右移 1 位。
根據以上規則可以整理成以下程式,比起原本的程式碼更有效率,可通過 LeetCode 477. Total Hamming Distance:
popcount(0xAAAAAAAA) = 16
所以可以得出 popcount(n ^ 0xAAAAAAAA) - 16
,但為了避免 n < 0
所以加上一個 3 的倍數變為 popcount(n ^ 0xAAAAAAAA) + 23
。
再做下一回合的運算以得到一個小於 3 的數:
經過上一回合後,n
目前的值介於 -3 ~ 2
,最後需要針對小於零的狀況加 3,且在原本程式中並沒有將 n
從 unsigned
轉型成 int
,由於沒有標示正負的符號,導致 & 3
的結果不如預期:
觀察可移動的方式,move_masks[0]
, move_masks[1]
, move_masks[2]
為第一排,如果 board
與這三個移動方式做 OR 運算,得到 0x70044444
,可以發現有連成一直線時,會有一個位元被設定成 7
,為了檢察是否為 7
,將 board + 0x11111111
在和 0x88888888
做 AND 運算就可以發現是否有人獲勝。
先將 x
的範圍縮減,x >> 15
移除較低的 15 位,(x & UINT32_C(0x7FFF))
只保留最低的 15 位。
根據函式註解,可以將缺漏的部分補上。