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 公式知( % 表示求模运算 )

3c≡3(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;
}
时间: 2024-12-19 09:19:16

n!最右非零数字的相关文章

非零缠绕规则和奇偶规则

在图形学中判断一个点是否在多边形内,若多边形不是自相交的,那么可以简单的判断这个点在多边形内部还是外部:若多边形是自相交的,那么就需要根据非零环绕数规则和奇-偶规则判断. 判断多边形是否是自相交的:多边形在平面内除顶点外还有其他公共点 内-外测试    不自交的多边形:多边形仅在顶点处连接,而在平面内没有其他公共点,此时可以直接划分内-外部分.    自相交的多边形:多边形在平面内除顶点外还有其他公共点,此时划分内-外部分需要采用以下的方法. (1)奇-偶规则(Odd-even Rule):奇数

hdu 1066 Last non-zero Digit in N! (数论——n!中的最后一个非0数字)

Last non-zero Digit in N! Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 6432    Accepted Submission(s): 1593 Problem Description The expression N!, read as "N factorial," denotes the pro

填充路径时所使用的 “非零环绕规则”

工作繁忙之际,抽了点时间看了下canvas,今天看到了“非零环绕规则”,抱着好奇的心里写了下书上的demo看了看效果,感觉还蛮实用的. 先简单说说“非零环绕规则”原理(基本摘自书本):如果绘图路径是循环的,或是包含多个相交的子路径,那么canvas的绘图环境变量就必须要判断,当fill()方法被调用时,应该如何对当前路径进行填充.canvas在填充那种互相有交叉路径时就会使用到“非零环绕规则”.“非零环绕规则是这么来判断自我交叉情况的路径的:对于路径中的任意给定的区域,从该区域内部画一条足够长的

Canvas中的非零围绕规则原理

非零围绕规则:对于路径中指定范围区域,从该区域内部画一条足够长的线段.使此线段的全然落在路径范围之外. 非零围绕规则计数器:然后,将计数器初始化为0,每当这个线段与路径上的直线或曲线相交时,就改变计数器的值,假设是与路径顺时针相交时.那么计数器就加1, 假设是与路径逆时针相交时.那么计数器就减1.假设计数器始终不为0,那么此区域就在路径范围里面,在调用fill()方法时,浏览器就会对其进行填充.假设终于值是0,那么此区域就不在路径范围内,浏览器就不会对其进行填充. 从上图中看第一条线段:依据非零

C#中下限非零的数组解析

谈到数组时,当被问及数组是从什么数开始时,估计大部分程序员都会直接说出数组当然是从0开始的.这个回答当然没有错,现在我们就来了解一下C#中的下限非0的数组. 首先看一下数组的相关介绍: 1.数组:是允许将多个数据项当作一个集合来处理的机制. 2.数组的分类:在CLR中,数组可分为一维数组,多维数组,交错数组. 3.数组的类型:由于所有的数组都是继承自System.Array这个抽象类型,而这个类型又是继承自System.Object,这就说明数组是引用类型. 在创建数组时,除了有数组元素,数组对

Canvas中的非零环绕规则原理

非零环绕规则:对于路径中指定范围区域,从该区域内部画一条足够长的线段,使此线段的完全落在路径范围之外. 非零环绕规则计数器:然后,将计数器初始化为0,每当这个线段与路径上的直线或曲线相交时,就改变计数器的值,如果是与路径顺时针相交时,那么计数器就加1, 如果是与路径逆时针相交时,那么计数器就减1.如果计数器始终不为0,那么此区域就在路径范围里面,在调用fill()方法时,浏览器就会对其进行填充.如果最终值是0,那么此区域就不在路径范围内,浏览器就不会对其进行填充. 从上图中看第一条线段:根据非零

验证非零的正整数

/** * 验证非零的正整数 * * @param 待验证的字符串 * @return 如果是符合格式的字符串,返回 <b>true </b>,否则为 <b>false </b> */ public static boolean IsIntNumber(String str) { String regex = "^\\+?[1-9][0-9]*$"; return match(regex, str); }

在论坛中出现的比较难的sql问题:22(字符串拆分、字符串合并、非连续数字的间隔范围、随机返回字符串)

在论坛中看到一个帖子,帖子中有一些sql方面的面试题,我觉得这些面试题很有代表性. 原帖的连接为:http://bbs.csdn.net/topics/390884161?page=1#post-398177057 下面是我的解法,供大家参考: 1.分拆字符串 create table test1 (number varchar(100)) insert into test1 values ('1,2,3,4,5,6') 希望结果: number ------ 1 2 3 4 5 6 (6 行受

Keil MDK下如何设置非零初始化变量(转)

源:Keil MDK下如何设置非零初始化变量 一些工控产品,当系统复位后(非上电复位),可能要求保持住复位前RAM中的数据,用来快速恢复现场,或者不至于因瞬间复位而重启现场设备.而keil mdk在默认情况下,任何形式的复位都会将RAM区的非初始化变量数据清零.如何设置非初始化数据变量不被零初始化,这是本篇文章所要探讨的. 在给出方法之前,先来了解一下代码和数据的存放规则.属性,以及复位后为何默认非初始化变量所在RAM都被初始化为零了呢. 什么是初始化数据变量,什么又是非初始化数据变量?(因为我