原题链接https://www.acwing.com/problem/content/1297/
算术基本定理
任何数都能质数积表示:
e.g.
一个数的最长表示方法就是:质因子从小到大
多重集合的排列数问题
以上面例子的为例,问2个2和3个3和1个5有多少种排列数,首先我们可以知道总的组合数为6!个,将所有的组合分组,不同的组代表不同的组合,例如2 2 3 3 3 5 和 2 3 3 3 2 5就是不同的组,我们要求的排列数实际就是有多少个组。因为2和3有重复,所以每个组都有
个重复的方案,即组的数量:
该公式可以推而广之,通用式我就不写了
欧拉筛(线性筛)O(n)
直接上代码看更清晰(这是普通的欧拉筛,本题的欧拉筛有改进增加了最小质因数的存储,可以往后看)
int prime[20000]; //存储素数的数组
int c=0; //记录prime中元素的个数(素数的个数)
bool isprime[20000]; //标记1-20000是不是素数1-是 0-不是
int n;
void EulerSevie()
{
//遍历2-20000的所有数字,都判断一遍
for(int i=2; i<=n; i++)
{
//是素数就入组
if(!isprime[i])
prime[++c]=i;
//遍历在<=i的所有素数
for(int j=1; j<=c&&i*prime[j]<=n; j++)
{
isprime[i*prime[j]]=true;//素数的倍数一定是合数 与埃氏筛不同的地方-埃氏是素数为外循环,所有数为内循环
//EulerSeive是所有数为外循环,素数为内循环,这样能及时判断避免重复运算
//如果prime[j]是i的因子,那么就认为prime[j]是i的最小质因子,就退出循环
//因为欧拉筛的原理就是通过最小质因子把质数筛出去,每一个i%prime[j]==0中的i都是合数那么就起码存在一个及以上的素数是它的因子
//我们只要计算最小的那个因子就可以,更大的在后面会被算(且是不能被省略的)那么前面的就多余了
//例如i=9,在prime[2]=3时就应该break,如果没有就会让isprime[i*prime[j]]=isprime[9*5]=isprime[45]=true
//但是在i=15时,在prime[2]时就会算到isprime[45],这是不可避免的但是在i=9时那个是可以省略的
if(i%prime[j]==0)
break;
}
}
}
本题分析
本题就是结合了上述三个数论,先利用欧拉筛得到素数的过程中,求得每个数的最小质因子,用minp数组存储。再利用算术基本定理将x分解成最长的质数组合,能分解多少个质数这个序列就有多长,再将这个序列进行组合,利用多重集合的排列数可以求出排列数也就是方案数。
AC Code
#include <iostream>
using namespace std;
const int N = (1<<20)+10;
/*
@isprime[i],1-不是素数 0-是素数
@primes[N] 存素数的数组
@minp[i] i的最小质因子
*/
int isprime[N],primes[N],minp[N]; //
int cnt;
void EulerSevie(int n)
{
for(int i=2; i<=n; i++)
{
if(!isprime[i])
{
minp[i] = i; //质数的最小质因子就是自己
primes[++cnt] = i;
}
for(int j=1; j<=cnt&&i*primes[j]<=n; j++)
{
int t=i*primes[j]; //t一定是合数 排除
isprime[t]=true;
minp[t]=primes[j]; //t的最小质因子
if(i%primes[j]==0) //如果prime[j]是i的最小质因子那么就退出循环 避免后续重复计算i*primes[j]
break;
}
}
}
int main()
{
int x,sum[N];
EulerSevie(N);
while(cin>>x)
{
//求x的质因子
int tot=0,k=0;
while(x>1)
{
int p=minp[x];
sum[k]=0;
while(x%p==0)
{
x /= p;
sum[k]++;
tot++;
}
k++;
}
long long res=1;
for(int i=1; i<=tot; i++)
res *= i;
for(int i=0; i<k; i++)
for(int j=1; j<=sum[i]; j++)
res /= j;
cout<<tot<<" "<<res<<endl;
}
return 0;
}