快速乘總結


快速乘總結

因為我們知道乘法有的時候會溢出,即使是 $ long~long $ 也可能在乘法時因為結果過大溢出(當模數也是 $ long~long $ )。所以我們需要尋找一種能高效完成乘法操作並且不會爆 $ long~long $ 的算法,也就是快速乘。本文也將對幾種常用快速乘及其優化技巧做個總結。

1. 復雜度為 $ O(log) $ 的快速乘:

我們知道乘法其實就是把很多個加法運算合到一起。現在我們的乘法會爆范圍,那我們就把它轉化為加法。但是我們不可能一個一個的加,這樣復雜度會是 $ O(n) $ 級別。所以我們模仿2進制加法操作來完成。

inline ll ksc(ll x,ll y,ll p){//計算x乘y的積
	ll res=0;//加法初始化
	while(y){
		if(y&1)res=(res+x)%p;//模仿二進制
		x=(x<<1)%p; y>>=1;//將x不斷乘2達到二進制
	}return res;
}
// ll 表示 long long

當然我們不一定要仿照2進制,也可以是其他進制,只要中間算每一位上數字代表值時不會爆 $ long long $ 就行!

2. 優秀的 $ STL $ 結構:__int128

__int128是c++自帶的一個數據類型,顧名思義,它可以裝下 $ 2^{128} $ 級別的大數據,而且可以直接進行各種加減乘除之類的操作(復雜度很接近 $ O(1) $ ),不過它需要手寫輸出(但其實我們只需要在運算時用一下就可以了,就像下面這樣:)

long long ans=((__int128)x*y)%p

不過有一點遺憾的就是:聯賽中基本上不會允許使用這個數據類型的

3. 非常優秀的 $ O(1) $ 快速乘

這個東西最初我感覺很不靠譜,但它就是能算出來正確答案。它就是用 $ long~double $ 來進行優化取模運算。讓我們先看一代碼實現吧:

inline ll ksc(ll x,ll y,ll p){
	ll z=(ld)x/p*y;
	ll res=(ull)x*y-(ull)z*p;
	return (res+p)%p;
}
// ll 表示 long long
// ld 表示 long double
// ull 表示 unsigned long long
// 一種自動溢出的數據類型(存滿了就會自動變為0)

看到這份代碼有沒有感到十分奇怪? 它中間是直接用了乘法操作的啊!這不直接爆掉了嗎?

但是它就是可以算出正確答案來。因為它其實很巧妙的運用了自動溢出這個操作,我們的代碼中的z就表示 $ \lfloor{x\times y/p}\rfloor $ ,所以我們要求的就變成了 $ x\times y-\lfloor{x\times y/p}\rfloor \times p $ ,雖然這兩個部分都是會溢出的,但(unsigned)保證了它們溢出后的差值基本不變,所以即使它會溢出也不會影響最終結果的!

4. 關於快速乘的靈活轉化:

我們知道快速乘的原理其實就是乘法轉加法(上面這種不算),但是這是可以根據題目性質靈活轉變的,我們如何轉成加法決定了我們的復雜度,就像如果模數並沒有超過int范圍很多,那我們適當的運用乘法分配律可以讓復雜度非常接近 $ O(1) $ :

inline ll ksc(ll x, ll y, ll P){
    ll L=x*(y>>25)%P*(1<<25)%P;
    ll R=x*(y&((1<<25)-1))%P;
    return (L+R)%P;
}

在保證運算不會爆long long的前提下,我們可以盡量優化其復雜度,就像上述代碼在模數小於 $ 10^{12} $ 的情況下完全變成了 $ O(1) $ 級別,在某些題目中會十分優秀!

5. 一些經常需要快速乘的算法:

$ Miller~rabin $ 判大質數

$ Pollard~Rho $ 大數因子尋找

$ BSGS $ 大步小步算法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM