培訓第一天——快速冪入門

1.從簡單的冪運算說起

對於一個接觸過一門編程語言的人來說,冪運算是一個最基礎的知識了,例如求2的20次方,我們只需要通過如下的代碼即可完成

int res = 1;
for (int i=0; i<20; i++)
{
	res *= 2;
}

現在考慮一下這個問題,如果20再大點呢? 200,20000,甚至2000000……
當然,用循環可以解決,但是花費的時間卻是以n的速度線性增長,n很大的時候,花的時間也是相當“可觀”。 這裏先插一句題外話 ,unsigned long long的最大值也就是2的64次方-1, 也就是說比這個數再大,基礎類型就已經存放不下了,如果要求2的200次方,肯定會溢出,所以這裏採取一個取模的運算,把最終的結果對1000取模,然後再輸出。
模運算
%就是取模的運算符,但如果像上面說的那樣,運算完之後再取模,這是很不現實的,當你中間算到某個值的時候,就overflow了,後面算的就都成了錯的,爲了避免這個問題,我們可以採用一個數學公式(對證明感興趣的朋友可以百度)

(A*B)%M = (A%M * B%M) % M (%一般是寫成mod,懂意思就行)

有了這個公式,我們可以在求冪的每一步都取模,這樣就不會有溢出了

接下來我們看一個例子:

	int ans = 1, num=2;
	for (int i=0; i<2000000000; i++)
	{
		ans = ans%1000 * num%1000;//求2的二十億次方
	}
	printf("%d\n", ans);

運行結果如下:
在這裏插入圖片描述
可以看出來,這花費的是時間還是相當長的,這對大型項目的影響還是很大的,很有必要提升效率!

2.快速冪

那麼,爲什麼會花這麼長的時間?肯定是由於循環的次數太多導致的。
有沒有什麼辦法減少循環的次數呢?
二十億太大了,但我們就得想辦法只循環一半次數,這樣做的代價就是要將2平方,也就是底數變成了4;
啊! 我們成功地把循環次數減少了一半,但十億也還大,我們可以接着把4接着平方,變成16,循環次數又減少了一半……直到經過不斷地平方求出了結果!

這就是快速冪核心的思想:二分法

當然,前面說的都是感性上的認識,接着我們用一個簡單的例子嚴謹地考慮一下
比如:求2的10次方,
首先,把指數10折半,也就是要求4的5次方, 到這一步,發現5沒辦法再折半了,雖然2.5次方在數學上有意義,但是在編程上確是講不通的。這時怎麼辦呢?我們可以提出來一個4,變成4*4的四次方,保留左邊的4,右邊就可以接着折半;那,4的4次方又可以變成16的平方;接着16的平方變成256,別忘了之前提出來的4,結果就是1024.我們總共做了

2 * 2=4,4 * 4=16,16 * 16=256,256*4=1024

四次乘法,更嚴格的來說是logn次,這裏n=10,那上面的2的二十億次方,也取一下對數,大概是30次吧,這真的是太神奇了,Amazing!

好了,扯了這麼多,該看看代碼了,根據上面一步步地計算,我們不難發現一個規律,指數爲奇數時,那個底數要提出來一個跟最後的結果相乘,然後進行折半偶數的時候直接進行折半。折半的最終結果是指數變成1,
換一種說法,我們需要一個變量來保存最後的結果,在快速冪的過程中,如果指數是偶數,我們就先不用管,讓它繼續去折半(遞歸),反正要的是最後的得數;如果是奇數的話,我們就需要去乘以這個底數(即提出一項),相當於一個保存的功能,因爲這些提出的項,最後肯定也還要乘以到結果中的。

根據上面的分析,我們來寫一下代碼

int quickPow(int x, int n, int mod)//O(logn)
{
	int ans = 1;//初始化
	while (n>0)
	{
		if (1==n%2)
			ans = ans%mod * x%mod;//取模
		n /= 2;
		x = x%mod * x%mod;
	}
	return ans;
}

代碼裏值得說明的地方:
1.除以是向下取整,無論n的奇偶性,直接除以二就行,這裏合併了兩種情況;
2.整個過程中不斷對n折半,也需要不斷對x平方,這點別忘
3.n=1的時候,就是折半結果了,剛好1也是奇數,所以循環條件是n>=1即n>0;

來測試一下這個程序, Amazing!!
在這裏插入圖片描述

3.另一種角度

理解了上面的,快速冪可以說已經學的比較到位了。爲了理解更透徹,下面就結合位運算再深入地探討一下。
分析一下上面的代碼,每一輪while循環,x都在不斷平方,x的變化過程如下:
在這裏插入圖片描述
這還看的不夠清楚,我們以3的13次方爲例,整個變化過程爲
在這裏插入圖片描述
然後模擬運行一下快速冪的程序,會發現最後ans的值爲
在這裏插入圖片描述
是不是想到了某種東西,別急,把指數補全看看

在這裏插入圖片描述
噢(恍然大悟),這不就是13的二進制展開形式嗎,就是這麼神奇!
這也就是說,我們可以用指數的二進制逐位進行判斷,1的話就跟ans相乘,0的話就接着往後走,可以想象成一個選擇過濾器,x在不斷地平方,每平方一次,都要根據指數二進制上對應的位(0或1)進行選擇性相乘,x只管走自己的,判斷讓指數來決定!
對應到代碼上改動比較小,一是把n%2 改成n&1, 二是把n%=2改成n>>=1,即移位運算符,雖然代碼差異不大,但理解確實兩個角度,直呼過癮!

int quickPow(int x, int n, int mod)
{
	int ans = 1;//初始化
	while (n>0)
	{
		if (n&1)
			ans = ans%mod * x%mod;//取模
		n>>=1;
		x = x%mod * x%mod;
	}
	return ans;

4.矩陣快速冪

最後談一下快速冪的拓展應用,冪運算也不是數字獨有的,定義了乘法的其他結構也可以,最典型的就是矩陣求冪,當然,根據線代的知識,方陣才能求冪,但這也不妨礙矩陣冪有很多的應用
先來說一下咋實現的:
問題的關鍵在於定義一個矩陣類型,然後定義它的乘法,取模等運算,到時候只需要把底數換成矩陣就行,沒有更多的要求,來看代碼:
1.矩陣類的實現


struct Matri{
    int a[15][15];
    int n;
    Matri (int N)
    {
        n = N;
        for (int i=0; i<n; i++)
            for (int j=0; j<n; j++)
                if (i==j)
                    a[i][j] = 1;
                else
                    a[i][j] = 0;
                
    }
    
    Matri operator*(Matri x)
    {
        Matri c(n);
        for (int i=0; i<n; i++)
        {
            for (int j=0; j<n; j++)
                c.a[i][j] = 0;
        }
        for (int i=0; i<n; i++)
        {
            for (int j=0; j<n; j++)
            {
                for (int k=0; k<n; k++)
                {
                    c.a[i][j] += a[i][k] * x.a[k][j];
                }
            }
        }
        return c;
    }
    Matri operator%(int x)
    {
        Matri c(n);
        for (int i=0; i<n; i++)
        {
            for (int j=0; j<n; j++)
            {
                c.a[i][j] = a[i][j]%x;
            }
        }
        return c;
    }
};

用了很多C++語言的特性,構造函數,重載運算符等,這些語言的細節就不再多說了,構造函數是在定義一個單位矩陣。
2.矩陣快速冪

Matri quickPow(Matri x, int n, int m)
{
    
    Matri res(x.n);
    while (n>0)
    {
        if (n&1)
        {
            res = res%m * x%m;
            res  = res%m;
        }
        x = x%m * x%m;
        x = x%m;
        n >>=1;
    }
    return res;
}

值得注意的一點是,在數字的快速冪中,我們的res定義的是1,是乘法的單位元,對於矩陣來說,單位矩陣便是“1”,因此,要通過構造函數建立一個單位矩陣。這點最容易出錯。

矩陣快速冪,最典型的應用就是求斐波那契數列,使複雜度降低到了logn,有些求斐波那契數列的題目會卡時間,到時候可以用矩陣快速冪進行優化。

今天的總結就先到這裏吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章