n!最右非零數字

n!最右非零數字

注此文大部分來自luoyuchu的blog
+ ##Description:

給出正整數N(可能有前導0),請求出N!最右非零的數位的值


+ ##Range:

n<=10^100

+ HDU1066 弱化問題USACO 3.2.1
以前做USACO暴力水過了 這是多麼的愚昧於是我去學習了一下
考慮慮到末位的0 是由於 2 * 5 這樣的運算而產生的,那麼我們把2與5成對的剔除就不會出現精度問題了?炸彈熊
其實問題還可以繼續深入思考。我們考慮在 10^1000 下如何解決問題
我們考慮一個分組問題
我們按10 分組 這樣是可以做的
我們任然可以按 奇偶分組,仍然是可以做的
任然還有按20分組的方案來自吉大的模板 超人熊
這道題其實就是抓住分組利用規律 進而利用一張小表 推出大表 這種思想是極好的

  • ##Solution:
    ####若設答案爲x,如果用C表示 [n/5] + [n/25] + [n/125] + …, 那麼需要求的是下面的同餘方程

n!x×10c(mod10c+1)

####*10^(c+1)*可分解爲*2^(c+1)*與*5^(c+1)*即爲

n!x×5c×2c(mod2c+1)
n!x×2c×5c(mod5c+1)

####當 n > 1 時所有的偶數都是上面的方程組中第一個方程的解,而且, n > 1 時第一個方程沒有奇數解,因此 n > 1 時只需要慮第二個方程的符合 0 < x <9 的偶數解:

n!x×2c×5c(mod5c+1)

####用 h(n) 表示 所有與 5 互素且不大於 n 的正整數的連乘積, 則 n! 可以表爲

h(n)×5[n/5]×h([n/5])×5[n/25]×h([n/25])×5[n/125]×h([n/125])×

####代入方程,消去 5 的乘方後得到下面的同餘方程

h(n)×h([n/5])×h([n/25])×x×2c(mod5)

####由於 3 * 2 ≡ 1 (mod5), 因此方程變爲

3c×h(n)×h([n/5])×h([n/25])×x(mod5)

####由 Euler-Fermat 公式知( % 表示求模運算 )

3c3(cmod4)(mod5)

####由 Wilson 定理有

h(n)(1)([n/5])×(nmod5)!(mod5)

####把上面兩式代入 (c) 就得到了 

3(cmod4)×(1)c×(nmod5)!×([n/5]mod5)!×([n/25]mod5)!×x(mod5)

####於是就可以通過O(logn)的時間求出答案
  • ##my_code:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <ctime>
#define dig(...) fprintf(stderr, __VA_ARGS__)
#define REP(i, n) for (int i = 1, _end_ = (n); i <= _end_; ++i)
const int mod = 10000;
const int maxs = 105;
const int pp[5] = {1, 6, 2, 8, 4};
using namespace std;
struct Bignum
{
    int w;
    int t[maxs];
    Bignum() {w = 0, memset(t, 0, sizeof(t));}
};
Bignum operator + (const Bignum &a, const Bignum &b)
{
    Bignum s;
    s.w = max(a.w, b.w);
    for (int i = 1; i <= s.w; ++i) s.t[i] = a.t[i] + b.t[i];
    for (int i = 1; i <= s.w; ++i)
    {
        if (i == s.w && s.t[i] >= mod) ++s.w;
        s.t[i + 1] += s.t[i] / mod;
        s.t[i] %= mod;
    }
    return s;
}
void divide(Bignum &s, int b, int &my)
{
    for (int i = s.w; i >= 1; --i)
    {
        s.t[i - 1] += mod * (s.t[i] % b);
        s.t[i] /= b;
        if (i == s.w && s.t[i] == 0) --s.w;
    }
    my = s.t[0] / mod;
    s.t[0] = 0;
}
int Ans = 1;
int n;
Bignum source;
void test(Bignum source)
{
    printf("%d", source.t[source.w]);
    for (int i = source.w - 1; i >= 1; --i) printf("%04d", source.t[i]);
    printf("----%d\n", source.w);
}
void Init()
{
    char t;
    int k;
    int w = 0;
    int num[maxs] = {0};

    memset(source.t, 0, sizeof(source.t));
    Ans = 1;

    while ((t = getchar()) < '1' || t > '9');
    do num[++w] = t - '0'; while((t = getchar()) >= '0' && t <= '9');
    REP(i, w / 2) swap(num[i], num[w - i + 1]);
    for (int i = 1; i <= w; i += 4)
    {
        k = 0;
        for (int j = i + 3; j >= i; --j) k = k * 10 + num[j];
        source.t[i / 4 + 1] = k;
    }
    source.w = (w + 3) / 4;
}
void Work()
{
    int my;
    Bignum c;
    Bignum s = source;
    do
    {
        divide(s, 5, my);
        c = c + s;
        REP(i, my) Ans *= i; Ans %= 5;
    }while (s.w);
    if (c.t[1] & 1) Ans *= -1;
    divide(c, 4, my);
    REP(i, my) Ans *= 3; Ans = (Ans + 1000) % 5;
    if (source.w == 1 && source.t[1] == 1) cout << pp[0] << endl;
    else cout << pp[Ans] << endl;
}
int main()
{
    int T;
    freopen("a.in", "r", stdin);
    freopen("a.out", "w", stdout);
    scanf("%d", &T);
    while (T--)
        {
        Init();

        Work();
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章