zoukankan      html  css  js  c++  java
  • 阶乘浅析poj1150 3406 zoj1222 2358

    阶乘问题分为几类:

    1.求阶乘末尾0的个数,,直接除以5,累加即可。

    2.求阶乘的结果一共有多少位,stirling公式:n!≈sqrt(2*PI*n) * (n/e)^n,直接取以10为底的对数,整数部分即为位数。http://poj.org 第1423题

    3.求阶乘的最后非零位,这类问题比较复杂,专题中我们着重讨论这个问题

     

    首先看POJ1150

    题目大意:求n的m排列的最后非零位。

    题目分析:n的m排列即P(n,m)=n!/(n-m)!,所以这题是求两个阶乘商的最后非零位。我们处理阶乘时一般是逐个处理。首先看普遍性的对于一个数n的阶乘,我们如何处理它的末尾非零位。

    10的因子是2和5,这两个数不属于模10的缩系,所以我们在处理数时要剔除掉这两个因子,剔除所有数中含5和2这两个因子。

    首先我们用f(n,x)表示小于等于n的数中,末尾位x的数的个数。n我们可以分解为1,3,5,7,9...  2,4,6,8,10...

    因为我们将偶数中的2因子剔除了,所以2,4,6,8,10...实际上又变成了1,3,5,7,9...

    所以我们可以得到递推式f(n,x)=f(n/2,x)+g(n,x),g(n,x)表示的是小于等于n的奇数中尾数为x的数的个数。

    那么g(n,x)怎么求呢,首先n/10必定是其中一部分,其次要看n>=x是否成立,成立就加1,不成立就不加。最后一点要注意,我们在奇数中还有汗5因子的数,所以要剔除掉5这个因子,比如5 15 25 35 45... 剔除了5之后又变成了1 3 5 7 9...,所以g(n,x)=n/10+(n%10>=x)+g(n/5,x)。

    有了这两个递推式再加上求5因子和2因子个数的递归除法,我们很快就能得到2因子个数,5因子个数,末尾位3 7 9因子的个数。下面我们要列个循环mod2[4]={6,2,4,8},mod3[4]={1,3,9,7},mod7[4]={1,7,9,3},mod9[4]={1,9,1,9}表示的是各个因子自己相乘时尾数的循环节,这里要特别注意一点,由于2非模10的缩系,所以2^0=1,这一点要格外注意,所以只有它循环节不是从0开始,0要单独处理。因为n-m的阶乘必定是n的阶乘的子集,所以我们可以直接用因子数相减即可。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    int cnt[8];
    int mod2[4]={6,2,4,8};
    int mod3[4]={1,3,9,7};
    int mod7[4]={1,7,9,3};
    int mod9[4]={1,9,1,9};
    int getpow(int n,int k)
    {
    	int sum=0;
    	while(n)
    	{
    		sum+=n/k;
    		n/=k;
    	}
    	return sum;
    }
    int g(int n,int k)
    {
    	if(n==0)return 0;
    	return n/10+(n%10>=k)+g(n/5,k); 
    }
    int get(int n,int k)
    {
    	if(n==0)return 0;
    	return get(n/2,k)+g(n,k);
    }
    int main()
    {
    	int n,m;
    	while(~scanf("%d%d",&n,&m))
    	{
    		int res=1;
    		memset(cnt,0,sizeof(cnt));
    		cnt[2]=getpow(n,2)-getpow(n-m,2);
    		cnt[5]=getpow(n,5)-getpow(n-m,5);
    		cnt[3]=get(n,3)-get(n-m,3);
    		cnt[7]=get(n,7)-get(n-m,7);
    		cnt[9]=get(n,9)-get(n-m,9);
    		if(cnt[2]>cnt[5]){res*=mod2[(cnt[2]-cnt[5])%4];}
    		else if(cnt[2]<cnt[5]){res*=5;}
    		res=res*mod3[cnt[3]%4]*mod7[cnt[7]%4]*mod9[cnt[9]%4];
    		printf("%d\n",res%10);
    	}
    	return 0;
    }
    


     

    再来看POJ3406

    求n!/(m!*(n-m)!)的最后非零位

    与上题一样,但是由于m的阶乘和n-m的阶乘的乘积并不是n的阶乘的子集,但是我们可以通过观察发现,只有n的阶乘中3的个数会少于m的阶乘和n-m的阶乘3的数目之和,但是我们不要担心,这个时候我们可以通过将n的阶乘中的9的个数分解得到。还有一种方法是再多加一个循环模(我表示不理解)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    int cnt[8];
    int mod2[4]={6,2,4,8};
    int mod3[4]={1,3,9,7};
    int mod7[4]={1,7,9,3};
    int mod9[4]={1,9,1,9};
    int getpow(int n,int k)
    {
    	int sum=0;
    	while(n)
    	{
    		sum+=n/k;
    		n/=k;
    	}
    	return sum;
    }
    int g(int n,int k)
    {
    	if(n==0)return 0;
    	return n/10+(n%10>=k)+g(n/5,k); 
    }
    int get(int n,int k)
    {
    	if(n==0)return 0;
    	return get(n/2,k)+g(n,k);
    }
    int main()
    {
    	int n,m;
    	while(~scanf("%d%d",&n,&m))
    	{
    		int res=1;
    		memset(cnt,0,sizeof(cnt));
    		cnt[2]=getpow(n,2)-getpow(n-m,2)-getpow(m,2);
    		cnt[5]=getpow(n,5)-getpow(n-m,5)-getpow(m,5);
    		cnt[3]=get(n,3)-get(n-m,3)-get(m,3);
    		cnt[7]=get(n,7)-get(n-m,7)-get(m,7);
    		cnt[9]=get(n,9)-get(n-m,9)-get(m,9);
    		cnt[3]+=cnt[9]*2;cnt[9]=0;
    		if(cnt[2]>cnt[5]){res*=mod2[(cnt[2]-cnt[5])%4];}
    		else if(cnt[2]<cnt[5]){res*=5;}
    		res=res*mod3[cnt[3]%4]*mod7[cnt[7]%4]*mod9[cnt[9]%4];
    		printf("%d\n",res%10);
    	}
    	return 0;
    }
    
    


    最后看一个BT题ZOJ1222

    这题在很多OJ上都有,只不过数量级很小,可以用上面的方法轻松ac,但是这题据说输入是一个字符串,可能有100+位,所以面对如此高精度大数,要用新的方法来解决。

    思路不变,还是以2和5为核心,把每个数最后的尾数乘起来,只是由于数的极速扩大,我们不能用原来的递归求解了,要用到一个循环节:int mod[10]={1,1,2,6,4,4,4,8,4,6};这个表示从0乘到9(将5换为1)的非0尾数。当n<5时,直接输出mod[n].我们要去掉尾数0,就要去掉相同数量的2和5,我们发现2的个数是远远多于5的个数,并且因子2的数目是最多的,所以最后得到的结果一定是一个偶数,于是存在这样一个特殊的尾数除法:2/2=6,4/2=2,6/2=8,8/2=4。同时在除以2的个数时,还是由于2的个数比较多的原因,是存在一个除法的循环节的,循环节的长度为4.并且这10个尾数相乘为6,当n>10时,我们可以得到一个公式:

     

    此时我们就可以应用这个公式进行求解了,值得注意的是n是一个非常大的数,在除以5的时候我们可以利用高精度除法。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    char str[1001];
    int mod[10]={1,1,2,6,4,4,4,8,4,6};
    int a[1001];
    int getAns(int cnt)
    {
    	if(cnt==1&&a[0]<5)return mod[a[0]];
    	int res=0;
    	int w=a[0];
    	for(int i=cnt-1;i>=0;--i)
    	{
    		res=res*10+a[i];
    		a[i]=res/5;
    		res=res%5;
    	}
    	if(a[cnt-1]==0)--cnt;
    	int m=(a[1]*10+a[0])%4;
    	int tmp=getAns(cnt)*mod[w]*6%10;
    	while(m--)
    	{
    		if(tmp==2)tmp=6;
    		else if(tmp==6)tmp=8;
    		else tmp/=2;
    	}
    	return tmp;
    
    }
    int main()
    {
    	while(~scanf("%s",str))
    	{
    		int len=strlen(str);
    		memset(a,0,sizeof(a));
    		for(int i=0;i<len;++i)
    		{
    			a[i]=str[len-1-i]-'0';
    		}
    		int ans=getAns(len);
    		printf("%d\n",ans);
    	}
    	return 0; 
    }
    

    4.数字n是否可以表示为若干个不同的阶乘的和

    实际上这个问题跟数论无关,是一道搜索的问题http://acm.zju.edu.cn的第2358题,由于n比较小,只可能是0!~9!的数字和,所以可以直接用dfs搜索。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <map>
    using namespace std;
    int a[10]={1,1,2,6,24,120,720,5040,40320,362880};
    int sum;
    bool vis[11];
    map<int,int>mymap;
    void dfs(int n)
    {
    	if(n==10)
    	{	
    		if(mymap.find(sum)==mymap.end())mymap[sum]=1;
    		return;
    	}
    	sum+=a[n];
    	dfs(n+1);
    	sum-=a[n];
    	dfs(n+1);
    }
    int main()
    {
    	memset(vis,false,sizeof(vis));
    	sum=0;
    	mymap.clear();
    	dfs(0);
    	int n; 
    	while(~scanf("%d",&n))
    	{ 
    		if(n<0)break;
    		if(n==0||mymap.find(n)==mymap.end())puts("NO");
    		else puts("YES");
    	}
    	return 0;
    }
    


     

  • 相关阅读:
    Message高级特性 & 内嵌Jetty实现文件服务器
    springboot中使用kindeditor富文本编辑器实现博客功能&vue-elementui使用vue-kindeditor
    Embarcadero RAD Studio XE5
    经典营销故事
    百度竞价教程 借助百度热力图让你的效果翻10倍
    无本借力:他是如何实现年收入70万?
    不用软件快速拥有几百个QQ群并都是管理员
    质保、保修、包修:含义不同
    域名反向解析在自建邮件群发服务器中的应用
    2014年1月1日,马年
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/2990473.html
Copyright © 2011-2022 走看看