因為我們知道乘法有的時候會溢位,即使是 $ long~long $ 也可能在乘法時因為結果過大溢位(當模數也是 $ long~long $ )。所以我們需要尋找一種能高效完成乘法操作並且不會爆 $ long~long $ 的演算法,也就是快速乘。本文也將對幾種常用快速乘及其優化技巧做個總結。
我們知道乘法其實就是把很多個加法運算合到一起。現在我們的乘法會爆範圍,那我們就把它轉化為加法。但是我們不可能乙個乙個的加,這樣複雜度會是 $ o(n) $ 級別。所以我們模仿2進製加法操作來完成。
inline ll ksc(ll x,ll y,ll p)return res;
}// ll 表示 long long
當然我們不一定要仿照2進製,也可以是其他進製,只要中間算每一位上數字代表值時不會爆 $ long long $ 就行!
__int128是c++自帶的乙個資料型別,顧名思義,它可以裝下 $ 2^ $ 級別的大資料,而且可以直接進行各種加減乘除之類的操作(複雜度很接近 $ o(1) $ ),不過它需要手寫輸出(但其實我們只需要在運算時用一下就可以了,就像下面這樣:)
long long ans=((__int128)x*y)%p
不過有一點遺憾的就是:聯賽中基本上不會允許使用這個資料型別的
這個東西最初我感覺很不靠譜,但它就是能算出來正確答案。它就是用 $ long~double $ 來進行優化取模運算。讓我們先看一**實現吧:
inline ll ksc(ll x,ll y,ll p)
// ll 表示 long long
// ld 表示 long double
// ull 表示 unsigned long long
// 一種自動溢位的資料型別(存滿了就會自動變為0)
看到這份**有沒有感到十分奇怪? 它中間是直接用了乘法操作的啊!這不直接爆掉了嗎?
但是它就是可以算出正確答案來。因為它其實很巧妙的運用了自動溢位這個操作,我們的**中的z就表示 $ \lfloor\rfloor $ ,所以我們要求的就變成了 $ x\times y-\lfloor\rfloor \times p $ ,雖然這兩個部分都是會溢位的,但(unsigned)保證了它們溢位後的差值基本不變,所以即使它會溢位也不會影響最終結果的!
我們知道快速乘的原理其實就是乘法轉加法(上面這種不算),但是這是可以根據題目性質靈活轉變的,我們如何轉成加法決定了我們的複雜度,就像如果模數並沒有超過int範圍很多,那我們適當的運用乘法分配律可以讓複雜度非常接近 $ o(1) $ :
inline ll ksc(ll x, ll y, ll p)
在保證運算不會爆long long的前提下,我們可以盡量優化其複雜度,就像上述**在模數小於 $ 10^ $ 的情況下完全變成了 $ o(1) $ 級別,在某些題目中會十分優秀!
$ miller~rabin $ 判大質數
$ pollard~rho $ 大數因子尋找
$ bsgs $ 大步小步演算法
快速乘總結
因為我們知道乘法有的時候會溢位,即使是 long long 也可能在乘法時因為結果過大溢位 當模數也是 long long 所以我們需要尋找一種能高效完成乘法操作並且不會爆 long long 的演算法,也就是快速乘。本文也將對幾種常用快速乘及其優化技巧做個總結。我們知道乘法其實就是把很多個加法運算...
快速乘學習總結
當兩個相乘數x y較小時,我們可以直接x y p。但當兩個數都為long long型別呢?很明顯直接相乘會爆範圍 其實乘法可以轉換成加法,我們可以邊加變取餘 但不是直接 y 個 x 乙個乙個相加,因為這樣絕對會超時。我們可以將 y 進行進製轉換 實現 ll q mod ll x,ll y,ll p ...
快速乘與快速冪總結
快速乘 a b p while b 把b看成二進位制 a a 2 p 2 式 要採用遞推 取模的方法得到 b b 1 右移一位 printf lld ans 推導過程 a 2 k1 a 2 k2 a 2 k3 a 2 kn p a 2 k1 a 2 k2 a 2 k3 a 2 kn 1 p a 2 ...