zoukankan      html  css  js  c++  java
  • The Last Non-zero Digit

    题意

    题目链接
    本题大意为 给定m,n

    求:(P^m_n)的最后一位非0的数

    (P在排列组合中就相当于A)

    (beican)历程

    看到题目 什么都不会的我一脸懵

    洛谷的限制太水了

    vjudge上时限1s

    我们按严格的来)

    但仔细想了一下 我仍然只发现暴力是不可以的

    我暴力的思路是 乘起来

    但直接乘会爆 就留个9位吧(感觉有点靠运气)

    这样的思路肯定是错的 (像这位小哥

    也不要想去优化 不然就像我搞了半天 仍然TLE

    接着 再仔细想想 发现求最后一位非0的数 只需要我们在算的过程中除10

    又知道 (P^m_n) = (frac{n!}{(n -m)!}) = (prod^n_{i=n-m+1})i

    换言之我们需要将含有因子2和5的除*掉

    BTW将因子2和因子5的数量统计

    因为一个10是由一个2和一个5乘得得

    直接除*掉不能保证因子2与因子5数量相等

    需要把多的乘回去(用快速幂)

    for(i = n;i > n - m;i--)
    	 {
    	    	int j = i;
    	    	while(j % 2 == 0)
    	    	{
    	    		j /= 2;
    	    		d2++;
    			}
    	    	while(j % 5 == 0)
    	    	{
    	    		j /= 5;
    	    		d5++;
    			}
    			
    			ans = (ans * j) % 10;
    		}
    if(d2 > d5)
    			More = qkpow(2,d2 - d5);
    		else if(d5 > d2) More = qkpow(5,d5 - d2);
    
    

    整体的代码大致如下

    #include <cstdio>
    #include <vector>
    #include <string>
    #include <iostream>
    #include <algorithm>
     
    using namespace std;
    
    long long d2,d5,More,ans;
    inline long long qkpow(int val,int n)
    {
    	long long rest = 1;
    	while(n)
    	{
    		if(n & 1)
    			rest = val * rest % 10;
    		val = val * val % 10;
    		n >>= 1;
    	}
    	return rest;
    }
    int main()
    {
        int i,n,m;
        while(~scanf("%d%d",&n,&m))
        {
        	d2 = d5 = 0;
        	More = ans = 1;
    	    for(i = n;i > n - m;i--)
    	    {
    	    	int j = i;
    	    	while(j % 2 == 0)
    	    	{
    	    		j /= 2;
    	    		d2++;
    			}
    	    	while(j % 5 == 0)
    	    	{
    	    		j /= 5;
    	    		d5++;
    			}
    			
    			ans = (ans * j) % 10;
    		}
    		if(d2 > d5)
    			More = qkpow(2,d2 - d5);
    		else if(d5 > d2) More = qkpow(5,d5 - d2);
    		printf("%lld
    ",ans * More % 10);
    	}
        return 0;
    }
    

    然后又TLE

    然后我不相信我不能成功

    又yy出来一个优化版本

    #include <cstdio>
    #include <vector>
    #include <string>
    #include <iostream>
    #include <algorithm>
     
    using namespace std;
    
    long long d2,d5,More,ans;
    inline long long qkpow(int val,int n)
    {
    	long long rest = 1;
    	while(n)
    	{
    		if(n & 1)
    			rest = val * rest % 10;
    		val = val * val % 10;
    		n >>= 1;
    	}
    	return rest;
    }
    int main()
    {
        int i,n,m;
        while(~scanf("%d%d",&n,&m))
        {
        	if(m == 0)
        	{
        		printf("1
    ");
        		continue;
    		}
        	d2 = d5 = 0;
        	More = ans = 1;
    	    for(i = n;i > n - m;i--)
    	    {
    	    	int j = i,k;
    	    	for(k = 1;!(j & k);k <<= 1,d2++);
    	    	j /= k;
    	    	while(j % 5 == 0)
    	    	{
    	    		j /= 5;
    	    		d5++;
    			}
    			ans = (ans * j) % 10;
    		}
    		if(d2 > d5)
    			More = qkpow(2,d2 - d5);
    		else if(d5 > d2) More = qkpow(5,d5 - d2);
    		printf("%lld
    ",ans * More % 10);
    	}
        return 0;
    }
    

    TLE

    正解1

    其实粗略估一下时间复杂度O(n(log_n)m)(m为询问数)

    0 <= N<= 20000000

    明显不行 (这题还有点坑,她没说询问数上线)

    然后我又想了下离线优化(没YY出来)

    最后 我又搞了搞事情 我写了个预处理

    #include <cstdio>
    #include <vector>
    #include <string>
    #include <iostream>
    #include <algorithm>
     
    using namespace std;
    const int MAXN = 20000001;
    int d2,d5,More,ans,a[MAXN][3];
    inline int qkpow(int val,int n)
    {
    	int rest = 1;
    	while(n)
    	{
    		if(n & 1)
    			rest = val * rest % 10;
    		val = val * val % 10;
    		n >>= 1;
    	}
    	return rest;
    }
    int main()
    {
        int i,n,m;
        for(i = 1;i < MAXN;i++)
        	if(i % 2 != 0&&i % 5 != 0) a[i][2] = i;
        	else {
        		if(i % 2 == 0) a[i][0] = a[i / 2][0] + 1,a[i][2] = a[i / 2][2]; 
        		if(i % 5 == 0) a[i][1] = a[i / 5][1] + 1,a[i][2] = a[i / 5][2];
    		}
    	a[i][2]存i去掉她的所有2,5因子后的值(如 i = 6 那么a[i][2] = 3)
    	a[i][0]存i含的因子2的数量
    	a[i][1]存i含的因子5的数量
        while(~scanf("%d%d",&n,&m))
        {
        	if(m == 0)
        	{
        		printf("1
    ");
        		continue;
    		}
    		int i,tot = 1,d2,d5;
    		d2 = d5 = 0;
        	for(i = n;i > n - m;i--)
        	{
        		tot = tot *a[i][2] % 10;
        		d2 += a[i][0];
        		d5 += a[i][1];
    		}
    		int More = 1;
    		if(d2 > d5) More = qkpow(2,d2 - d5);
    		else More = qkpow(5,d5 - d2);
    		tot = tot * More % 10;
    		printf("%d
    ",tot);
    	}
        return 0;
    }
    

    然后惨痛MLE(空间超限)(请妳不要用看hen**tai 的眼神看我)

    估一下空间

    众所周知 一int占4字节

    1b(byte) = 1 字节

    我用了20000000 * 3 * 4b

    即240000kb

    但是题目限制65536 kB

    (color{red} ext{阿勒})

    我没超限啊?

    不管了 错了就是错了 事实如此 无需诡辩

    然后去看题解

    好多长得一样的 (我不知道为什么,真de不知道)

    然后写得一点都不富有亲和力

    (于是我决定详详细细地写一篇(好像已经很长了))

    但是 我们可以考虑用short int来存

    a[i][2]只存i去掉她的所有2,5因子后的值的个位

    a[i][0]存i含的因子2的数量

    a[i][1]存i含的因子5的数量

    a[i][0] 可以 与 a[i][1]相等部分可以抵消(所以a[i][0] 和 a[i][1]中始终有一个是0)

    易发现

    (2^1) = 2 (2^2) = 4 (2^3) = 8 (2^4 = 6) (2^5 = 2)

    以上都是mod 10后的 可以发现有循环产生

    5也有相同情景 就可以采用某种神奇的存储方式

    达到用short int存的效果

    这里就点到为止 不再展开(感兴趣的同学可以向这个方向想一下)

    我是没有想出来的

    妳才没有被坑

    让我们把精力放在真正的正解上(滑稽

    正解2

    思路不变仍然要统计2,5的个数

    不过不枚举 直接从整个阶乘去求

    inline int kiyo(int n,int div)
    {
    	if(n == 0) return 0;
    	return (n / div + kiyo(n / div,div));
    }
    

    div为求的数

    如 求 5!中含因子2的个数

    kiyo(5,2)

    return 0;很好理解

    但 为什么return (n / div + kiyo(n / div,div));

    n / div 是针对1~n的含div的个数

    但考虑到 kiyo(5,2) 中有4这种情况

    4中有两个2但n / div只统计了一次

    kiyo(n / div,div)就是为了统计未统计到的

    举个栗子kiyo(10,2)

    1 2 3 4 5 6 7 8 9 10

    0 1 0 1 0 1 0 1 0 1第一次n / div

    1 2 3 4 5第二次n / div
    0 1 0 1

    但由于第二次是除了一次2的

    所以实际算的是

    2 4 6 8 10
    0 1 0 1 0

    类似的

    最终可得

    1 2 3 4 5 6 7 8 9 10

    0 1 0 2 0 1 0 3 0 1 8

    再用 kiyo(n,div)- kiyo(n - m,div)

    在logn的时间复杂度内求出2,5的个数

    也许有同学会想到 那么再求出3,7的个数就行了啊

    因为 偶数都可以转化为对应的奇数

    而 1 3 5 7 9中

    1不管

    9是3的倍数

    5已经求了

    把7,3求一下

    OK!

    然后 华丽地爆零 瞬间成为奥林匹斯山上的宙斯

    Why?

    因为 13 17 等虽不是3,7的倍数 却有3,7

    所以 求的应该是末位是3,7,9的的数的个数

    以下即为求法

    get(a,b)即求1~a中有多少末位为b的

    inline int odd(int n,int div)
    {
    	if(n == 0) return 0;
    	return n / 10 + (n % 10 >= div) + odd(n / 5,div);
    	按以下分步的
    	1  2   3  4  5  6  7  8  9  10
    	11 12  13 14 15 16 17 18 19 20
    	....
    	n+1 n+2...
    	所以是先 n / 10 代表有几层就有几个末位是div的
    	但考虑到可能n是10a+b(b<10)的形式就有可能还有1个
    	 所以(n % 10 >= div)
    	但以上没有考虑5
    	如15/5=3
    	而除以5的操作是之前进行的 这里没有记录15仍然是15
    	所以还需 odd(n / 5,div)跟上面kiyo含意类似
    	类似地 同学可能会说6不是还要除以2吗 为什么不再调用一个odd(n / 2,div)
    	因为get()已经解决了该问题
    }
    inline int get(int n,int div)
    {
    	if(n == 0) return 0;
    	return (get(n / 2,div) + odd(n,div));
    	分成奇偶两个数列
    	因为算的都是1~n
    	所以只有两种情况
    	Case1:n ≡ 0 (mod 2)
    	可以分为
    	1 3 5...n-1
    	这边(上面)的就可以调odd()函数算了
    	为什么odd()的不除以2呢? 因为odd()默认算奇数
    	2 4 6...n
    	进行除2操作类似于odd的除5操作
    	Case2:n ≡ 1 (mod 2)
    	1 3 5...n
    	2 4 6...n-1
    	其实与Case1差不多int a = b / 2;
    	b为奇的话 就相当于(b-1)/2
    	不影响get(n / 2,div)
    	odd(n,div)直接传的n多的那一位也有考虑在内
    }
    

    由此可以得到含因子2,5的数量

    和1~n中末位为3,7,9的数量

    还需

    - get(n - m,3)
    

    因为求的是(P^m_n) = (frac{n!}{(n -m)!}) = (prod^n_{i=n-m+1})i

    Code(正解2的)

    #include <cstdio>
    #include <vector>
     
    using namespace std;
    
    inline int qkpow(int val,int n)
    {
    	int rest = 1;
    	while(n)
    	{
    		if(n & 1)
    			rest = val * rest % 10;
    		val = val * val % 10;
    		n >>= 1;
    	}
    	return rest;
    }
    inline int kiyo(int n,int div)
    {
    	if(n == 0) return 0;
    	return (n / div + kiyo(n / div,div));
    }
    inline int odd(int n,int div)
    {
    	if(n == 0) return 0;
    	return n / 10 + (n % 10 >= div) + odd(n / 5,div);
    }
    inline int get(int n,int div)
    {
    	if(n == 0) return 0;
    	return (get(n / 2,div) + odd(n,div));
    }
    int main()
    {
    	int n,m;
        while(~scanf("%d%d",&n,&m))
        {
        	无限输入
        	int ans = 1;
        	int t2 = kiyo(n,2) - kiyo(n - m,2);
        	int t5 = kiyo(n,5) - kiyo(n - m,5);
        	计算含2,5因子数
        	int t3 = get(n,3) - get(n - m,3);
        	int t7 = get(n,7) - get(n - m,7);
        	int t9 = get(n,9) - get(n - m,9);
        	算末位为3,7,9的数的个数
        	if(t2 > t5) ans = ans * qkpow(2,t2 - t5) % 10;
        	else ans = ans * qkpow(2,t5 - t2) % 10;
        	ans = ans * qkpow(3,t3) % 10;
        	ans = ans * qkpow(7,t7) % 10;
        	ans = ans * qkpow(9,t9) % 10;
        	套快速幂 logn
        	好像在这里还有O(1)的做法 不太懂 感兴趣的同学可以去看看其它博客
        	printf("%d
    ",ans);
    	}
        return 0;
    }
    

    恭喜妳

    又能AC一题

  • 相关阅读:
    架构基础-容量评估
    golang版本实现版本号比较-从易到解决bug
    数组模拟栈
    稀疏数组
    密码生成器
    01-gopsutil包使用
    02从零开始学习GO语言--标识符、关键字、变量和常量
    Go语言简介
    从零开始学习GO语言-搭建Go语言开发环境-快速开发入门第一个小程序
    ES6学习总结之 Module
  • 原文地址:https://www.cnblogs.com/resftlmuttmotw/p/11323195.html
Copyright © 2011-2022 走看看