这道题比赛的时候没有写出来。。没有写出来是想不出如何验证这个数是不是符合题目要求的,写不出check函数。
参考大佬博客:https://blog.nowcoder.net/n/aa5ff9efa80440c897a9aaae4401a467
卡住我的两个点:
1、怎么分解出p的因数
可以通过一个for和while的嵌套:for循环从2开始枚举,每次枚举令p除到不能再被i整除(不含因数i)的时候位置,因为一个合数肯定是由比它小的素数相乘的来的,因此,for循环枚举i的时候,枚举到合数的时候他是会自动跳过的,因为已经枚举过这个合数的质因数了,所以现在的p自然也就没办法被合数i整除了。
inline void deal(ll p){//pr为map<ll,int>,记录每一个质因数的频数 int cntt=0; for(int i=2;i*i<=p;i++){//一直枚举到 while(p%i==0){//若p还含有因数i,则继续除下去 pr[i]++; p/=i; } } if(p!=1){//这里是为了防止p=2这个特殊情况 pr[p]++; } }
2、怎么验证n符不符合条件
上面我们已经把p分解质因数了,这里,我们只需要验证n!中分解出来的因数个数能不能够大于p所具有的因数的个数。计算方法是:对于目前要检查的因数now,我们可以用一个while循环:当mid>=now的时候,说明,mid!当中还含有now或now的倍数,这时候,我们可以用一个while循环,sum+=mid/now求出一部分,后mid/now,知道mid<now.
举个栗子:
对于80!,我们可以数出来含有7的因数12个。
80!含有7的因数的项由7 14 21 28 35 42 49 56 63 70 77
除以一次7之后:1 2 3 4 5 6 7 8 9 10 11,sum=11,p=11
可以看到,里面仍然有一个7,再除一次,sum=12,p=1<7,结束循环。
这里可以这么想:含有因数7的一定是7的倍数。我们每一次除出来的结果,都是在这个数出在这个范围内由多少个7的倍数,每个数被数了一次(上面第一行的数都被数了一次)。n/=7后,还剩下的就是原来或以上次的数,这些数还含有一个7的因数,还能再除,以此类推,还有算……一直到n比7小,也就是为止。可以自己再好好感受下。
inline bool check(ll m){ ll pos=0; for(auto it=pr.begin();it!=pr.end();it++){ ll now=it->first; ll n=m,sum=0; while(n>=now){ sum+=n/now; n/=now; } if(sum<it->second) return false; } return true; }
完整代码:
二分就没什么好讲了。
#include <iostream> #include <vector> #include <cstring> #include <map> using namespace std; typedef long long ll; const int maxn=1e6+10; map<ll,int> pr; int cnt=0; inline void deal(ll p){ int cntt=0; for(int i=2;i*i<=p;i++){ while(p%i==0){ pr[i]++; p/=i; } } if(p!=1){ pr[p]++; } } inline bool check(ll m){ ll pos=0; for(auto it=pr.begin();it!=pr.end();it++){ ll now=it->first; ll n=m,sum=0; while(n>=now){ sum+=n/now; n/=now; } if(sum<it->second) return false; } return true; } int main() { int t; scanf("%d",&t); while(t--){ pr.clear(); ll p; scanf("%lld",&p); deal(p); ll ans=1; ll l=1,r=1e9; while(l<=r){ ll m=(l+r)>>1; if(check(m)) ans=m,r=m-1; else l=m+1; } printf("%lld\n",ans); } return 0; }