POJ1150 The Last Non-zero Digit - 數論 - 模運算

The Last Non-zero Digit

Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 5783   Accepted: 1810

Description

In this problem you will be given two decimal integer number N, M. You will have to find the last non-zero digit of the NPM.This means no of permutations of N things taking M at a time.

Input

The input contains several lines of input. Each line of the input file contains two integers N (0 <= N<= 20000000), M (0 <= M <= N).

Output

For each line of the input you should output a single digit, which is the last non-zero digit of NPM. For example, if NPM is 720 then the last non-zero digit is 2. So in this case your output should be 2.

Sample Input

10 10
10 5
25 6

Sample Output

8
4
2

 
 

題目大概意思:

求組合數 CNMC_N^M 在十進制下的末尾非零位的值。其中 0MN2×1070≤M≤N≤2×10^7 .

 
 

分析:

首先容易想到時間複雜度爲 O(n)O(n) 的算法:

先計算出 CNMC_N^M 的質因子 2255 的指數,分別記爲 cnt2,cnt5cnt_2,cnt_5 ,則 CNMC_N^M 的因子 1010 的指數爲 min(cnt2,cnt5)\min(cnt_2,cnt_5) ,記爲 cnt10cnt_{10} ,那麼 (i=NM+1Ni)/10cnt10mod&ThinSpace;&ThinSpace;10{(\prod_{i=N-M+1}^N{i})}/{10^{cnt_{10}}}\mod10 即爲所求,再線性地計算 i=NM+1Ni10cnt10\frac{\prod_{i=N-M+1}^N{i}}{10^{cnt_{10}}} 其中在逐個相乘的過程中,用每一次儘可能令當前的 ii 除以 2255 ,直到 2255 都除夠了 cnt10cnt_{10} 次。

然而 NN 的取值範圍較大,且題目爲多組案例,這樣的解法無法達到時間要求。
 

接下來我們思考,當一個數 xx 乘以另外一個數 yy 後, xx 的末尾非零位會如何變化:

  1. 如果 yy 的末尾是 0011 則乘以這個數無論如何都不會對末尾非零位造成影響
  2. 如果 yy 的末尾是 {3,7,9}\{3,7,9\} 中的一個,則 (xy)mod&ThinSpace;&ThinSpace;10=(xmod&ThinSpace;&ThinSpace;10)(ymod&ThinSpace;&ThinSpace;10)mod&ThinSpace;&ThinSpace;10(xy)\mod{10}=(x\mod10)·(y\mod10)\mod10 ,即 xyxy 的末尾非零位就等於 xx 的末尾非零位與 yy 的末尾的乘積的末尾
  3. 如果 yy 的末尾是 {2,4,6,8}\{2,4,6,8\} 中的一個,若 xx 不是 55 的倍數,則與第 22 種情況相同
  4. 同理,如果 yy 的末尾是 55 ,若 xx 不是 22 的倍數,則與第 22 種情況相同

因此如果我們能得到區間 [NM,M][N-M,M] 中的每一個整數在除去因子 22 與因子 55 後的末尾位(這些末尾位只可能是 {1,3,7,9}\{1,3,7,9\} 中的一個),那麼讓這些末尾位在mod&ThinSpace;&ThinSpace;10\mod10 的狀態下相乘得到 xx&#x27; ,在讓 xx&#x27; 乘以 cnt2cnt10cnt_2-cnt_{10}22cnt5cnt10cnt_5-cnt_{10}55 得到 xx (實際上只乘了 2255 中的一個若干次,因爲 cnt10=min(cnt2,cnt5)cnt_{10}=\min(cnt_2,cnt_5)),則 xx 的末尾就是所求了。在計算 xx&#x27; 的過程中,始終滿足前兩種情況,即只乘 {1,3,7,9}\{1,3,7,9\} ;在由 xx&#x27; 計算 xx 的過程中,始終滿足後兩種情況,因爲 xx 不會同時是 2255 的倍數。因此, CNMC_N^M 的末尾非零位的值就等於以上這些數的末尾在mod&ThinSpace;&ThinSpace;10\mod10 的狀態下相乘。

其中 cnt2cnt_2cnt5cnt_5 可以通過計算 N!N! 的質因子指數與 (NM)!(N-M)! 的質因子指數相減得到,而 N!N! 的質因子 ff 的指數可以以如下方式計算出來:(證明略)

// 計算 x! 的質因子 f 的指數
int cnt_fact(int x, const int f)
{
	int res = 0;
	while (x)
	{
		res += x / f;
		x /= f;
	}
	return res;
}

 
 
接下來考慮除去因子 2,52,5 後末尾爲 {1,3,7,9}\{1,3,7,9\} 的數的個數的計算,不妨分別記爲 cnt1,cnt3,cnt7,cnt9cnt_1,cnt_3,cnt_7,cnt_9 . 由於個數的可加性,計算區間 (NM,N](N-M,N] 中的 cntkcnt_k 同樣可以通過區間 [1,N][1,N] 的個數減去 [1,NM][1,N-M] 的個數得到。

由於我們只需要計算區間 (NM,N](N-M,N] 中每一個整數在除去因子 22 與因子 55 後的末尾位的乘積,因此無需對區間中的每一個數進行計算,只需計算區間中的數在除去因子 2,52,5 後以 1,3,7,91,3,7,9 爲末尾的個數即可。

那麼如何計算區間 [1,N][1,N] 中的數除去因子 2,52,5 後以 1,3,7,91,3,7,9 爲末尾的個數呢?

首先考慮區間中奇數的計算,對於那些末尾爲 kk 的數,可以直接計入統計,而對於末尾爲 55 的,則需要除以 55 後再進行判斷,如果這些末尾爲 55 的數的集合中的每個數除以 55 後,仍存在末尾爲 55 的,則需要再除以 55 ,因此可以遞歸地進行判斷,如下爲代碼:

// 計算所有≤x的奇數在除去因子5後, 以 end(end∈{1,3,7,9}) 結尾的數的個數
int cnt_odd_end(int x, int end)
{
	if (x < end) return 0;
	return x / 10 + (x % 10 >= end) + cnt_odd_end(x / 5, end);
}

 
而對於區間中的偶數,由於我們統計的是除去因子 2255 後的末尾位,因此只需將這些數全部除以 22 後將偶序列轉化爲奇偶交替的序列,並把奇序列用 cnt_odd_endcnt\_odd\_end 函數統計,偶序列遞歸處理即可,如下爲代碼:(這裏省去了對偶序列的單獨處理,而是直接寫爲了對整個序列的處理)

// 計算所有≤x的數在除去因子2和5後, 以 end(end∈{1,3,7,9}) 結尾的數個數
int cnt_end(int x, const int end)
{
	if (x < end) return 0;
	return cnt_odd_end(x, end) + cnt_end(x / 2, end);
}

 
 
在最後將所有數乘在一起的時候,可以採用快速冪的方法降低時間複雜度。在整個算法中,統計 cnt2cnt_2cnt5cnt_5 的時間複雜度是 O(log2N)O(\log_2{N}) ;計算以 {1,3,7,9}\{1,3,7,9\} 爲末尾的個數時, cnt_endcnt\_end 函數遞歸調用了 log2N\log_2N 次,每一次會調用一次 cnt_odd_endcnt\_odd\_end ,而 cnt_odd_endcnt\_odd\_end 函數的時間複雜度是 O(log2N)O(\log_2N) . 因此算法的總時間複雜度是 O(log22N)O(\log_2^2{N}) .

 
 
下面貼出完整代碼:

#include <cstdio>
#include <algorithm>
using namespace std;


int cnt_fact(int x, const int f);
int cnt_end(int x, const int end);
int cnt_odd_end(int x, int end);
int mod_pow(int x, int p, const int m);

int main()
{
	int N, M;
	while (~scanf("%d%d", &N, &M))
	{
		int cnt2 = cnt_fact(N, 2) - cnt_fact(N - M, 2);
		int cnt5 = cnt_fact(N, 5) - cnt_fact(N - M, 5);

		int end3 = cnt_end(N, 3) - cnt_end(N - M, 3);
		int end7 = cnt_end(N, 7) - cnt_end(N - M, 7);
		int end9 = cnt_end(N, 9) - cnt_end(N - M, 9);

		int cnt10 = min(cnt2, cnt5);
		cnt2 -= cnt10;
		cnt5 -= cnt10;

		int res = mod_pow(3, end3, 10) * mod_pow(7, end7, 10) * mod_pow(9, end9, 10) * mod_pow(2, cnt2, 10) * mod_pow(5, cnt5, 10) % 10;
		printf("%d\n", res);
	}
	return 0;
}

// 計算 x! 的質因子 f 的指數
int cnt_fact(int x, const int f)
{
	int res = 0;
	while (x)
	{
		res += x / f;
		x /= f;
	}
	return res;
}

// 計算所有≤x的數在除去因子2和5後, 以 end(end∈{1,3,7,9}) 結尾的數個數
int cnt_end(int x, const int end)
{
	if (x < end) return 0;
	return cnt_odd_end(x, end) + cnt_end(x / 2, end);
}

// 計算所有≤x的奇數在除去因子5後, 以 end(end∈{1,3,7,9}) 結尾的數的個數
int cnt_odd_end(int x, int end)
{
	if (x < end) return 0;
	return x / 10 + (x % 10 >= end) + cnt_odd_end(x / 5, end);
}

// 計算 x^p % m
int mod_pow(int x, int p, const int m)
{
	int res = 1;
	while (p)
	{
		if (p & 1)
		{
			res = res * x % m;
		}
		x = x * x % m;
		p >>= 1;
	}
	return res;
}

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