- 链接 : 题目
- 题意 : 给定一个p, 找一个最小的n,使得n!是p的倍数。
- 思路 :有两种思路
1、二分方法,因为要找到最小的n!,用二分即可。重点便是二分里的judge函数。
(1)此题要用到分解质因子的方法。
算数基本定理 : 任意一个大于1的自然数N,如果N不为质数,则N可以唯一分解为有限个质数的乘积。
比如 100 = 2 * 2 * 5 * 5。
这里我们要用map<int, int> 这个容器,first是每个质因数,2和5,second是出现的次数,将100分解后,map里存在的是 {2,2,} 和 {5, 2}。
然后我们需要判断一个 mid 的阶乘是否是100的倍数,就要看mid !里面质因子2的个数 >= 2, 5的个数 >= 2。
(2) 那么如何判断 n!里面质因子的个数呢?
n!里面质因子p的个数,1-n中包含质因子p的数的个数。
n!中至少包含一个质因子p的个数是 n / p,至少包含两个的个数是n / p^2……
比如 mid = 8,判断8!里面质因数2的个数。
首先 8! 里面有2 4 6 8 四个数可以整除2,此个数便是8 / 2 = 4;除2 后 8!里面,就变成了 1 2 3 4,其中2 和 4可以整除2,此个数便是 4 / 2 = 2; 除2后只有一个2, 此个数便是 2 / 2 = 1。 所以 8! 里面 质因子2的个数便是 4 + 2 + 1 = 7。
(3)代码:
#include "bits/stdc++.h"
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define pb push_back
// #define _DEBUG
typedef long long ll;
const int maxn = 2e5 + 7;
const int INF = INT_MAX; //常量代替 0x7fffffff
int t,p;
map<int,int> mp;
void init(int p){
//分解质因数并记录在map里
mp.clear();
for (int i = 2; i*i <= p; i++) {
// i*i 是一个优化,因为p的因子最大是 根号p
while (p % i == 0) {
mp[i] ++;
p /= i;
}
if (p == 1) {
break;
}
}
if (p != 1) {
//p本身是质数
mp[p] = 1;
}
}
bool ok(int mid){
for (map<int,int>::iterator it = mp.begin(); it != mp.end(); it++) {
int prime = it->first, num = it->second;
int cnt = 0, n = mid; //cnt是每个质因数的个数
while (n) {
cnt += n / prime;
n /= prime;
}
if (cnt < num) {
return false;
}
}
return true;
}
int main(){
#ifdef _DEBUG
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
cin>>t;
while (t--) {
cin>>p;
init(p);
int l = 1, r = p;
int ans = 0;
while (l <= r) {
int mid = l + (r - l) / 2;
if (ok(mid)) {
r = mid - 1;
ans = mid; //每次更新ans
}else{
l = mid + 1;
}
}
cout<<ans<<endl;
}
return 0;
}
(4)遇到的问题 :
对p分解质因子的时候,注意是 i*i <= p ,否则会TLE。这是一种类似埃氏筛法的小技巧,具体可以看如下链接:
欧拉筛法
二分里面,如果r = mid 而不是 mid-1,会死循环,所以就直接mid + 1和mid - 1这样,同时最后的l 或者 r可能不是最后答案,就用ans每次更新答案。
2、同样是分解质因子的方法,不过是用gcd
(1)最大公因子 Greatest Common Divisor
此函数在刘汝佳大神的《算法竞赛入门经典》中有讲到。辗转相除法。
int gcd(int a,int b){
if(b == 0) return a;
return gcd(b,a%b);
}
(2)如果p是1或者质数,则直接输出p;
if (p == 1 || isPrime(p)) {
cout<<p<<endl;
}else{
for (int i = 2; ; i++) {
if (gcd(i,p) != 1) {
p /= gcd(i,p);
if (p == 1) {
cout<<i<<endl;
break;
}
if (i < p && isPrime(p)) {
cout<<p<<endl;
break;
}
}
}
}
i 从2开始遍历,如果i 和p 有最大公因子, p /= gcd(i,p),如果p = 1,那么直接输出这个i; 如果此时i < p, 并且p是质数,则直接输出p。这是一个剪枝,因为p 是质数,i < p,那么i 递增到p后才会满足 p == 1,所以直接输出p。
其思想便是分解质因子,因为i是从2开始递增的,2——i ,相当于是 i 的阶乘,每个数判断是否有p中的质因子,如果有就更新p,最后 p = 1则说明 此时的i的阶乘便是p的倍数。这样最后输出的i 便满足条件。
(3)代码:
#include "bits/stdc++.h"
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define pb push_back
typedef long long ll;
const int maxn = 1e4 + 7;
const int INF = INT_MAX;
// #define _DEBUG
int t,p;
bool isPrime(int p){
if(p == 1) return false;
for (int i = 2; i*i <= p; i++) {
if(p % i == 0) return false;
}
return true;
}
int gcd(int a,int b){
if(b == 0) return a;
return gcd(b,a%b);
}
int main(){
#ifdef _DEBUG
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
cin>>t;
while (t--) {
cin>>p;
if (p == 1 || isPrime(p)) {
cout<<p<<endl;
}else{
for (int i = 2; ; i++) {
if (gcd(i,p) != 1) {
p /= gcd(i,p);
if (p == 1) {
cout<<i<<endl;
break;
}
if (i < p && isPrime(p)) {
cout<<p<<endl;
break;
}
}
}
}
}
return 0;
}