传送门
题意:
让你找到一个最小的p=n!p=n!p=n!使得ppp能够式子∏i=1kai!\prod_{i=1}^{k}ai!∏i=1kai!整除,并输出nnn。
题目分析:
非常棒的一个题目!
首先,因为要求得一个最小的满足条件的值,因此,我们不妨可以想到可以使用二分。而对于这个问题,倘若n!n!n!能够被∏i=1kai!\prod_{i=1}^{k}ai!∏i=1kai!整除,则可知,(n+1)!(n+1)!(n+1)!也可以被∏i=1kai!\prod_{i=1}^{k}ai!∏i=1kai!整除,由此可知我们所求的答案具有一定的单调性,因此我们可以用二分算出答案。
对于整除性的问题有一个套路就是,我们可以把需要求的值进行素因数分解,分别统计每一个素因子的个数,最后我们只需要在二分答案的同时,将二分的值同时也进行素因数分解。最后我们只需要判断两次的素因子的个数进而进行二分边界的转移。
但是在这个题中,上述的做法的瓶颈在于我们不能用比较优的时间分解所有数的阶乘的质因数。因此此时需要部分优化。
我们可以设数组cnt[i]cnt[i]cnt[i]为iii这个数的出现次数。那么我们可以根据所给的aia_iai统计出他们分别的出现次数。继而我们可以从后往前枚举所有的数,倘若cnt[i]cnt[i]cnt[i]有值,则根据阶乘的性质,很显然cnt[1…i−1]cnt[1 \dots i-1]cnt[1…i−1]都应该增加cnt[i]cnt[i]cnt[i]次。(即如果cnt[4]cnt[4]cnt[4]为1,则cnt[1]cnt[1]cnt[1]、cnt[2]cnt[2]cnt[2]、cnt[3]cnt[3]cnt[3]必定也会出现一次)。
统计完每个数的出现次数之后,我们就可开始分解质因数。首先我们发现,如果一个数为素数则当前的答案即是最终的结果。而倘若当前的数numnumnum为合数,则证明该数能够被分解,那么我们就将它分解为num′=numminprime[num]num'=\frac{num}{minprime[num]}num′=minprime[num]num,其中minprime[num]minprime[num]minprime[num]为该数numnumnum的最小质因数;同时我们需要把分解了的数num′num'num′和minprime[num]minprime[num]minprime[num]的出现次数分别加上cnt[num]cnt[num]cnt[num],(因为要满足阶乘,则在num′num'num′和minprime[num]minprime[num]minprime[num]也必定出现cnt[num]cnt[num]cnt[num]次)
至此,我们就可以用O(n)\mathcal{O}(n)O(n)的时间复杂度线性的求出所有质因数的出现次数。
之后我们只需要进行二分即可。
代码:
#include
using namespace std;
typedef long long ll;
const int maxn=1e7+10;
bool isprime[maxn];
int minp[maxn],k,tot;
int prime[maxn];
ll sum=0;
void getPrime(){
memset(isprime,1,sizeof(isprime));
for(int i=2;i<maxn;i++){
if(isprime[i]){
minp[i]=i,prime[tot++]=i;
for(int j=2*i;j<maxn;j+=i){
if(isprime[j]) minp[j]=i;
isprime[j]=false;
}
}
}
}
int a[maxn];
ll cnt[maxn];
ll res[maxn];
bool judge(ll mid){
memset(res,0,sizeof(res));
for(int i=0;i<tot;i++){
ll t=prime[i];
while(mid/t){
res[prime[i]]+=mid/t;
t*=prime[i];
}
}
for(int i=0;i<tot;i++){
if(cnt[prime[i]]>res[prime[i]]) return false;
}
return true;
}
int main()
{
int n;
getPrime();
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
sum+=a[i];
cnt[a[i]]++;
}
for(int i=maxn-1;i>=2;i--){
if(cnt[i]) cnt[i-1]+=cnt[i];
}
for(int i=maxn-1;i>=2;i--){
if(!isprime[i]){
cnt[i/minp[i]]+=cnt[i];
cnt[minp[i]]+=cnt[i];
}
}
ll l=0,r=sum;
ll ans=0;
while(l+1<r){
ll mid=(l+r)>>1;
if(judge(mid)) r=mid;
else l=mid;
}
cout<<r<<endl;
}