有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 相乘
那么这个问题跟我们的题目有没有联系呢?没有联系我说它干嘛
如果我们把这个例子拓展一下,就可以得到如下推论:
我们就用那组{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
#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…随缘吧博主没什么发言权,但一般来说都跳不出欧拉定理、组合数学以及莫比乌斯反演那些个专题知识的大坑(吧。。)若是在考场上实在想不出来,也一定不能根据正确性未知的局部推理进行编码,至少也要分个段打点暴力分。
简单的(像这种)可以通过打表找规律,自己手算几组数据得到普遍规律,还有一些比较基础的推理需要记忆。就从这道题来看,无非就是考了个质因数分解+小学奥数,但博主依然做不来,由此可以看出我太弱了对于基础知识掌握的不牢固,还有对于数论板块没有一个全面的了解以及深度的刷题,这方面还应该多下功夫才是。。。。