排列数与组合数非零最低位 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.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
毕设新项目-基于Java开发的智慧养老院信息管理系统源码+据库(含vue前端源码).zip 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、据科学与大据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我! 毕设新项目-基于Java开发的智慧养老院信息管理系统源码+据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+据库(含vue前端源码).zip毕设新项目-基于Java开发的智慧养老院信息管理系统源码+据库(含vue前端源码).zip
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值