【校内模拟】【18-10-05】阶乘(数论)

【题目描述】

有n个正整数a[i],设它们乘积为p,你可以给p乘上一个正整数q,使p*q刚好为正整数m的阶乘,求m的最小值。

【输入】

共两行。
第一行一个正整数n。
第二行n个正整数a[i]。

【输出】

共一行,一个正整数m。

【输入样例】

1
6

【输出样例】

3

【样例解释】

当n=6,q=1时,p*q=3!

【数据范围与约定】

对于%10的数据,n<=10

对于%30的数据,n<=1000

对于%100的数据,n<=100000,a[i]<=100000

【题解】

博主对于数论题一向很头疼。。。这种东西若是能找到规律就很容易打出正解,要是脸黑经验不够丰富看不出来就很只能暴力了。。。比如这次就凉凉了

错误的推理(跳过好了

首先我们随便拿组数据过来实验一下(比如什么{2 4 6 8 11}之类的),容易发现几个有趣的地方:

①如果一串数中最大的是质数,m就是这个质数的可能性很大(比如11)

②如果一串数中最大的是合数,那么我们能把它拆成更小的几个因数相乘,从而尽量把m变小。(比如样例)

③如果一个数出现n次,那么这个数很危险无论是质数还是合数,都不可能作为m,而且在这个数够大的情况下,m极有可能是它的n倍(比如{5 5 5 11},出现三个5,最终的m也就是15)

然后据此思路乱搞一通可以轻轻松松拿到0分~

正确的分析

对于上面三条完全错误的分析,均可以举出一堆反例来证明它们,但是总结一下,我们很容易看出这道题的核心全部牵扯在两个关键词上:因数,质数。

那我们当然是要非常愉快的分解一波质因数了,毕竟a[i]最大只有十万,复杂度并不会很高。

然后呢?现在我们得到了所有a[i]的质因数,我们要把这堆东西乘上一个值,让它成为某个数的阶乘。

(往下就是蒟蒻的博主想不到的了)

我们来看一个有趣的问题:
27!里面有多少个 3 相乘?
27!=12…*27
包含 1 个 3 的数有 27/(3^1)=9 个
包含 2 个 3 的数有 27/(3^2)=3 个
包含 3 个 3 的数有 27/(3^3)=1 个
总共:9+3+1=13 个
所以 27!里面有 13 个 3 相乘

那么这个问题跟我们的题目有没有联系呢?没有联系我说它干嘛

如果我们把这个例子拓展一下,就可以得到如下推论:

对于一个给定的正整数M和另一个给定的质数N,只需要M ÷ \div ÷N次运算,就可以得到!M中N的个数

我们就用那组{2,4,6,8,11}来打个比方,分解之后我们可以得到7个2,1个3,1个11
那么我们把这个问题分解一下,就是找一个满足以下条件的m:

① m!里至少能分解出7个2 ② m!里至少能分解出1个3 ③ m!里至少能分解出1个11

现在我们把刚才的那个数学推论套用一下,三个条件变成了这样:

①m>=8 ②m>=3 ③m>=11

所以这个问题就变得非常简单了,只要取最大的那个就可以了

不过,如果对于每一个质因数n,都去找到一个满足(m!至少包含K个n)的m,最后再来取最大值,由于数据范围实在不小,万一分解出几百个甚至几千个2,这样的处理无疑会超时。

所以换一种思路,我们用二分枚举m,再检验它包含的各个质因数的个数是否合法,可以达到同样的效果。

那么我们就可以非常愉悦的AC了~

#include<bits/stdc++.h>
#define endll '\n'
#define rint register int 
#define ll long long
#define ull unsigned long long
#define ivoid inline void 
#define iint inline int 
using namespace std;    
const int N=20e5+5;
const int M=2000;
ll a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;
ll mx,mx_r,cnt,tot,ans,num_pri,sqr;
ll not_pri[N],pri[N],pri_tot[N],pri_kind[N];
ll rad()
{
   ll ret=0,f=1;
   char c;
   for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar())
   if(c=='-')
   f=-1,c=getchar();
   for(;c>='0'&&c<='9';c=getchar())
   ret=(ret<<3)+(ret<<1)+c-'0';
   return ret*f;
}
       
ivoid init()//这里用的是埃式筛法,换成欧式筛速度会更快 
{
   for(rint i=2;i<=100000;i++){
   	if(!not_pri[i])pri[++tot]=i;
   	for(rint j=1;j<=tot&&pri[j]*i<=100000;j++){
   		not_pri[pri[j]*i]=1;
        	if(!i%pri[j]) break;
   	}
  	}
}
   
ivoid divide(ll x)
{ 
   if(!not_pri[x]){//如果是质因数就退出,不是就继续递归分解
   	pri_tot[x]++;
       mx=max(x,mx);
   	return;
   }//找到最大的那个质因数,作为下面check的上界 
   sqr=sqrt(x);
   for(rint i=1;i<=tot;i++){
      	if(pri[i]>sqr)return;	
      	if(!(x%pri[i])){//递归处理 
      		divide(pri[i]),//这一句改成 pri_tot[i]++,mx=max(x,mx) 是一样的 
   		divide(x/pri[i]);
       	return;  	
       }
   }
}
   
iint check(ll x)
{
   for(rint i=1;i<=mx;i++){
   	k=pri_tot[pri[i]];//至少需要 k个pri[i]出现 
   	if(!k)continue;//这个质因数没出现过,直接跳过 
   	//否则,我们就开始计算二分出的x的阶乘中有多少个pri[i]; 
   	num_pri=0;
   	for(rint j=pri[i];x>=j;j*=pri[i]){//x中包含的pri[i]的个数严格小于x 
   		num_pri+=x/j;
   		if(num_pri>=k)break;//包含的个数已经超过所需个数就跳出,节约时间 
   	}
   	if(num_pri<k)return false;
   }
   return true;
}
   #define read(x) x=rad()
int main()
{
   read(n);
   init();
   for(rint i=1,a;i<=n;i++){
       read(a);
       divide(a);//分解质因数 
   	if(pri[a]==0)//统计不同的质因数的个数 
   	pri_kind[++cnt]=a;
       pri_kind[a]++;
   }
   ll l=1,r=1000000000;//二分大法好啊 
   while(l<r-1){
       int mid=(l+r)/2;
   	if(check(mid)==1)r=mid;
   	else l=mid;
   }
   ans=l; 
   if(check(ans+1)==1)l=ans+1;//以防万一。。。去掉也没什么影响(吧) 
   if(check(ans)==1)l=ans;
   if(check(ans-1)==1)l=ans-1;
   cout<<l;
   return 0;
} 

总结一下:
数论题无非那么几种做法,

高级的emm…随缘吧博主没什么发言权,但一般来说都跳不出欧拉定理、组合数学以及莫比乌斯反演那些个专题知识的大坑(吧。。)若是在考场上实在想不出来,也一定不能根据正确性未知的局部推理进行编码,至少也要分个段打点暴力分。

简单的(像这种)可以通过打表找规律,自己手算几组数据得到普遍规律,还有一些比较基础的推理需要记忆。就从这道题来看,无非就是考了个质因数分解+小学奥数,但博主依然做不来,由此可以看出我太弱了对于基础知识掌握的不牢固,还有对于数论板块没有一个全面的了解以及深度的刷题,这方面还应该多下功夫才是。。。。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值