考虑将 k 分解质因数,对于每项 pr,都要求 n! 中含有至少 r 次 p。由于 n! 的质因数单调增加,所以可以二分。对于检查某个 n 是否满足条件,只需要看 n 以内有多少个 p 的倍数 + n 以内有多少个 p2 的倍数……以此类推。对于每个 pr 取对 n 限制最大的那个即可,最后可能剩下的一个 >n 的质因数特判一下。时间复杂度 n+log3(n)。
检查 n! 中 p 的次数使用的方法被称为“勒让德定理(Legendre’s Formula)”,描述是: n! 中 p 的次数等于 k≥1∑⌊pkn⌋。证明:首先对于所有的 p 的倍数都要计算一次贡献,为 ⌊pn⌋;然后对于所有 p2 的倍数,我们希望计算两次贡献,而当前只计算了一次,所以需要再加上 ⌊p2n⌋ 的贡献。以此类推得到上述结论。
勒让德定理有一个扩展形式,描述为:n! 中 p 的次数等于,n 减去“将 n 转化为 p 进制后的各位之和”,再除以 p−1。证明:容易发现 ⌊pn⌋ 即为 n 在 p 进制下右移一位的结果,⌊p2n⌋ 即为 n 在 p 进制下右移两位的结果,以此类推。故对于 n 的第 i 位(从低到高),它会分别在第 i−1,i−2,i−3,⋯,0 位统计贡献,故第 i 位 ei 造成的贡献为 j<i∑ei⋅pj=ei⋅p−1pi−1,而总贡献即为每一位的贡献之和(x 为位数):
voidsolve(){ int k;cin>>k; auto p = pollard_rho_fast::factorize2(k); int lb=0, ub = 1e12+5; while(ub-lb>1){ int mid=(lb+ub)/2; for(auto x:p){ int num=x.a; int cnt=x.b; int t=0; int now=mid; while(now){ t += now / num; now /= num; } if(t < cnt) goto fail; } ub=mid;continue; fail:;lb=mid; } o(ub); } signedmain(){ cin.tie(0); ios::sync_with_stdio(0); cout<<fixed<<setprecision(20); int t; t = 1; //cin >> t; while(t--) solve(); }
voidchmax(longlong* a, longlong b) { if (*a < b) *a = b; }
intmain() { longlong K; scanf("%lld", &K);
int j; longlong i, ans = 1, tmp, tmpp; for (i = 2; i * i <= K; i++) { if (K % i != 0) continue; for (j = 0; K % i == 0; j++, K /= i); for (tmp = i; 1; tmp += i) { for (tmpp = tmp / i, j--; tmpp % i == 0; tmpp /= i, j--); if (j <= 0) break; } chmax(&ans, tmp); } if (K >= 2) chmax(&ans, K); printf("%lld\n", ans); fflush(stdout); return0; }
考虑直接枚举 n,维护当前 n! 对 k 的模数,直到第一次为 0 时满足条件。这个做法直接写肯定会超时,但注意到当 n 比较大时就将大部分情况覆盖地非常全了,唯一一种没有覆盖到的情况只能是 k 有一个非常大的质因子。此时该质因子只能出现一次(因为 >k),所以答案的 n= 该质因子。
正确性证明:
假设 k 在 k 以内的某个质因子 pr,则在枚举 n 时至少会在每个 p 的倍数被覆盖一次,所以 n 保证正确性的枚举上界为最大的 pr。有 r=logp(k),所以枚举上界为 plogp(k),其在 p>2 时单调递增,所以最坏情况有 {p=2,r=log2(k)p=k,r=2,所以枚举到 2k 即可保证正确性。