排列數與組合數非零最低位 POJ 1150 POJ 3406

題目傳送門:http://poj.org/problem?id=1150
http://poj.org/problem?id=3406

1150的題意是給你一個nPm, 求這個數從低位開始找第一個非零的數字。

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

int table[4] = {6, 2, 4, 8};
int step[10] = {0,0,1,-1,2,-1,0,1,-1,2};    
const int mod = 4;

int getstep(int x)
{
    int s = 0;
    while (x) {
        s = (s + x / 5) % mod;
        if (x % 5 == 2) s = (s + 1) % mod;
        if (x % 5 == 4) s = (s + 2) % mod;
        x /= 5;
    }
    return s;
}

int main()
{
    int n,m;

    while (~scanf("%d%d",&n,&m)) {
        if (n <= 1 || m == 0) {
            printf("1\n");
        } else if (m == 1) {
            printf("%d\n",n);   
        } else {
            printf("%d\n",table[(getstep(n) + 4 - getstep(n - m)) % mod]);
        }
    }
    return 0;
} 

暴力一定超時,從網上找了一篇blog,真的很牛。
http://www.tuicool.com/articles/naaUZ3

大致的意思是每對當前的中間過程的得數 乘一個數,會對最後一位施加一個影響,對於>1的數字的階乘,其最後一位一定是2 4 6 8中的一個,不妨令table[] = {6, 2, 4, 8}(2的4次方,1次方,2次方,3次方),而每乘一個數就是對當前最後非零位的一次改變,對應在table這個表中就體現爲左移,右移或者不動,這個規律也很好找,試一下就好了,就是代碼中的step。

由於排列數P(n,m) = n!/(n - m)! 所以計算出n!移動的步數,在計算出(n-m)!移動的步數,就可以得到結果了。

#include <cstdio>
#include <iostream>
using namespace std;
int n,m;
int table[4][4] = {
    6, 2, 4, 8,
    1, 3, 9, 7,
    1, 7, 9, 3,
    1, 9, 1, 9,
};
int getprime(int n, int x)
{
    int ret = 0;
    while (n) {
        ret += n / x;
        n /= x;
    }
    return ret;
}

int getodd(int n, int x)
{
    if (n == 0) return 0;
    return getodd(n / 5, x) + (n % 10 >= x) + (n / 10);
}
int getend(int n, int x)
{
    if (n == 0) return 0;
    return getend(n / 2, x) + getodd(n, x);
}

int main()
{
    while (~scanf("%d%d",&n,&m)) {
        m = n - m;
        int two = getprime(n,2) - getprime(m,2);
        int five = getprime(n,5) - getprime(m,5);
        int three = getend(n,3) - getend(m,3);
        int seven = getend(n,7) - getend(m,7);
        int nine = getend(n,9) - getend(m,9);
        int ans = 1;
        if (five > two) {
            puts("5");
            continue;
        }
        ans = ans * table[1][three % 4] % 10;
        ans = ans * table[2][seven % 4] % 10;
        ans = ans * table[3][nine % 4] % 10;
        if (two > five) {
            ans = ans * table[0][(two - five) % 4] % 10;
        }
        printf("%d\n",ans);
    }
}

這種方法是比較通用的方法,通過計算n!的因子數來確定最後一位。首先要先把所有因子爲10的全部去掉,這樣後面就不會有0了,但10不是質數,並不容易找出,不妨 找出全部因子爲2和因子爲5的個數,這樣就可以找到因子爲10的個數了。剔除2和5之後,剩下的數字一定是末尾爲3 7 9的數字的積,而這些數字自身的乘方末尾數字是有循環節的,最後不要忘了把多餘的因子2補上,而2的乘方末位也是循環的。

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

int n,m;
int table[4][4] = {
    6,2,4,8,
    1,3,9,7,
    1,7,9,3,
    1,9,1,9,
};

int getprime( int n, int x)
{
    int ret = 0;
    while (n) {
        ret += n / x;
        n /= x;
    }
    return ret;
 } 
int getodd (int n, int x)
{
    if (n == 0) {
        return 0;
    } else {
        return (n / 10) + (n % 10 >= x) + getodd(n / 5, x);
    }
}
int getend (int n, int x)
{
    if (n == 0) return 0;
    return getend(n / 2, x) + getodd(n, x);
}
int main()
{
    while (~scanf("%d%d",&n,&m)) {
        int two = getprime(n, 2) - getprime(m, 2) - getprime(n - m, 2);
        int five = getprime(n, 5) - getprime(m, 5) - getprime(n - m, 5);
        if (five > two) {
            puts("5");
            continue;
        }
        int three = getend(n, 3) - getend(m, 3) - getend(n - m, 3);
        int seven = getend(n, 7) - getend(m, 7) - getend(n - m, 7);
        int nine = getend(n, 9) - getend(m, 9) - getend(n - m, 9);
        three += 2 * nine; nine = 0;
        int ans = 1;
        ans *= table[1][three % 4];
        ans *= table[2][seven % 4];
        ans *= table[3][nine % 4];
        if (two > five) {
            ans *= table[0][(two - five) % 4];
        }
        printf("%d\n",ans % 10);
    }
    return 0;
}

而對於組合數,第一種方法就不適用了,因爲末位數字不一定就是table中的數字了,所以使用了第二種方法,不過要注意的是,組合數中分母寫成階乘形式,末位爲3的個數可能會比分子上的多,所以我們把9拆開,拆成兩個3.

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