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
題目大概意思:
求組合數 在十進制下的末尾非零位的值。其中 .
分析:
首先容易想到時間複雜度爲 的算法:
先計算出 的質因子 與 的指數,分別記爲 ,則 的因子 的指數爲 ,記爲 ,那麼 即爲所求,再線性地計算 其中在逐個相乘的過程中,用每一次儘可能令當前的 除以 與 ,直到 與 都除夠了 次。
然而 的取值範圍較大,且題目爲多組案例,這樣的解法無法達到時間要求。
接下來我們思考,當一個數 乘以另外一個數 後, 的末尾非零位會如何變化:
- 如果 的末尾是 或 則乘以這個數無論如何都不會對末尾非零位造成影響
- 如果 的末尾是 中的一個,則 ,即 的末尾非零位就等於 的末尾非零位與 的末尾的乘積的末尾
- 如果 的末尾是 中的一個,若 不是 的倍數,則與第 種情況相同
- 同理,如果 的末尾是 ,若 不是 的倍數,則與第 種情況相同
因此如果我們能得到區間 中的每一個整數在除去因子 與因子 後的末尾位(這些末尾位只可能是 中的一個),那麼讓這些末尾位在 的狀態下相乘得到 ,在讓 乘以 個 , 個 得到 (實際上只乘了 或 中的一個若干次,因爲 ),則 的末尾就是所求了。在計算 的過程中,始終滿足前兩種情況,即只乘 ;在由 計算 的過程中,始終滿足後兩種情況,因爲 不會同時是 與 的倍數。因此, 的末尾非零位的值就等於以上這些數的末尾在 的狀態下相乘。
其中 與 可以通過計算 的質因子指數與 的質因子指數相減得到,而 的質因子 的指數可以以如下方式計算出來:(證明略)
// 計算 x! 的質因子 f 的指數
int cnt_fact(int x, const int f)
{
int res = 0;
while (x)
{
res += x / f;
x /= f;
}
return res;
}
接下來考慮除去因子 後末尾爲 的數的個數的計算,不妨分別記爲 . 由於個數的可加性,計算區間 中的 同樣可以通過區間 的個數減去 的個數得到。
由於我們只需要計算區間 中每一個整數在除去因子 與因子 後的末尾位的乘積,因此無需對區間中的每一個數進行計算,只需計算區間中的數在除去因子 後以 爲末尾的個數即可。
那麼如何計算區間 中的數除去因子 後以 爲末尾的個數呢?
首先考慮區間中奇數的計算,對於那些末尾爲 的數,可以直接計入統計,而對於末尾爲 的,則需要除以 後再進行判斷,如果這些末尾爲 的數的集合中的每個數除以 後,仍存在末尾爲 的,則需要再除以 ,因此可以遞歸地進行判斷,如下爲代碼:
// 計算所有≤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的數在除去因子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);
}
在最後將所有數乘在一起的時候,可以採用快速冪的方法降低時間複雜度。在整個算法中,統計 與 的時間複雜度是 ;計算以 爲末尾的個數時, 函數遞歸調用了 次,每一次會調用一次 ,而 函數的時間複雜度是 . 因此算法的總時間複雜度是 .
下面貼出完整代碼:
#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;
}