Factorial Factors Problem Description
Given a positive integer N, we could effectively figure out all the factors of N,as N1,N2…NK are aggregate k factors of N(including 1 and N itself).
This problem is somewhat complicated. According to these k factors, we respectively calculate up the number of factors of these factors. For example, N1 may have n1 factors (including 1 and N1), N2 may have n2 factors, …, and NK may have nk factors.
Now comes the problem, I want to find out the answer of S = n13 + n23 + …+ nk3.input
The first line of input there is one integer T (T<=10000),giving the number of test cases in the input. For each test case, there is only a positive integer N (N < 231)
ouput
For each test case, output one line with the answer S.
Sample Input
2
6
9Sample Output
81
36
题目的意思是,给出一个正整数N,该正整数N有k个因子N1,N2,N3…Nk,而其中的每一个Ni,例如N1,他又有k1个因子,比如n1,n2,n3。题目要求的是对于给定的正整数N,求他的每一个因子的因子数的立方和。即n13 + n23 + …+ nk3.
1.原本的思路:一开始我采用的是暴力法,即求出N的所有因子数后,再根据每一个因子利用 质因数分解(后面会用到) 求出因子的因子数,求立方加到sum中。这样显然会超时,后来在此基础上想了一个优化的方法,即利用求素数筛的思路,写一个因子筛子,只要O(nlognlogn)的时间即可求出n以内所有正整数的因子数,但此题数据量高达2^31,所以只能制作小部分的因子筛,而在不考虑空间的情况下,光是要制作一个231的因子筛,就需要远远超过1分钟的时间。这个思路行不通!
2.正确是思路:在苦苦思索之际,一个想法涌入我的脑海中,我开始思考质因数分解求因子数的原理
对于一个正整数N,如果N不是素数,则N是合数,如果N是合数,则N一定可以被分解为N个质数相乘。比如12可以看成2个2和1个3相乘,即12=2231。
用上面的12做例子,12=2231,观察这个式子,我们发现,他主要有2项(2和3),其中2可以出现0,1,2次(3种可能),3可以出现0,1次(2种可能),根据乘法原理,就可能出现2×3=6种因子组合,即:2030, 2031, 2130, 2131, 2230, 2231,由此我们推测,12的因子数等于(2+1)*(1+1) = 6。
了解了完质因数求因子数的原理,我们离成功就只有一步之遥啦!还是拿12做例子,12分解为6个因子2030, 2031, 2130, 2131, 2230, 2231后,我们发现,我们发现,这六个因子恰好又是他们本身的质因数分解形式,从质因数分解形式我们直接可以看出他们分别有(0+1)(0+1)=1,(0+1)(1+1) = 2, (1+1)(0+1) = 2, (1+1)(1+1) = 4, (2+1)(0+1) = 3, (2+1)(1+1) = 6个因子,我们把这些结果的立方和加起来,求得324即为答案。
#include
#include
#include
#include
#include
#include
#include
#define MAXN 10
typedef unsigned long long ll;
using namespace std;
int a[MAXN], na;
ll pow_mod(ll a,ll b,ll r)
{
ll ans=1,buff=a;
while(b)
{
if(b&1)
ans=(ans*buff)%r;
buff=(buff*buff)%r;
b>>=1;
}
return ans;
}
bool test(ll n,ll a,ll d)
{
if(n==2) return true;
if(n==a) return false;
if(!(n&1)) return false;
while(!(d&1)) d>>=1;
ll t = pow_mod(a,d,n);
while(d!=n-1&&t!=n-1&&t!=1){
t = t*t%n;//下面介绍防止溢出的办法,对应数据量为10^18次方;
d<<=1;
}
return t == n-1||(d&1)==1;//要么t能变成n-1,要么一开始t就等于1
}
bool isPrime(ll n)
{
if (n <= 1) return false;
int a[] = {2,3,5,7};//或者自己生成[2,N-1]以内的随机数rand()%(n-2)+2
for(int i = 0; i <= 3; ++i){
if(n==a[i]) return true;
if(!test(n,a[i],n-1)) return false;
}
return true;
}
ll next_Prime(ll x){
x++;
if (x%2 == 0) x++;
while (!isPrime(x)) x += 2;
return x;
}
long long sum;
void f(int cur, int t){
if (cur == na+1){
sum += t*t*t;
return ;
}
for (int i = 0; i <= a[cur]; i++){
f(cur+1, t*(i+1));
}
}
void solve(ll x){
if (x == 1){
sum = 1;
return ;
}else if (isPrime(x)){
sum = 9;
return ;
}
ll nowPrime = 2;
sum = 0;
na = 0;
memset(a, 0, sizeof(a));
while (x > 1 && (!isPrime(x))){
if (x%nowPrime == 0){
x /= nowPrime;
a[na]++;
}else{
/* do{
nowPrime = next_Prime(nowPrime);
}while (x%nowPrime);*/
if (nowPrime == 2) nowPrime++;
else nowPrime += 2;
while (1){
if (x%nowPrime == 0){
if (isPrime(nowPrime)){
break;
}
}
nowPrime += 2;
}
na++;
}
}
if (isPrime(x)){
if (nowPrime == x){
a[na]++;
}else{
a[++na]++;
}
}
sort(a, a+na);
f(0, 1);
}
int main(){
int N, i, j;
ll x;
scanf ("%d", &N);
while (N--){
scanf ("%lld", &x);
solve(x);
printf("%lld\n", sum);
}
return 0;
}
以上代码使用了Miller Rabin算法判断素数,该算法是一种概率判素算法,存在一定错误的可能性,但迭代次数k超过5之后,错误的可能性几乎为0,而且相对于普通的O(sqrt(n))算法来说,具有极其优秀的时间复杂度O(klog2(n))。在本题中,使用Miller Rabin算法程序仅耗时85ms,而使用普通的判素算法也能通过,但用时600+ms,不够优美。
另外,思路正确了,如果质因数分解部分代码太暴力也十分容易超时,比如上面AC代码中,注释的部分,每次都要判断是否为素数,会造成大量计算资源被浪费(我在这个地方卡了很久),而使用未注释的代码,先看nowPrime是否能被整除,如果能被整除再判断是否为素数,情况就好很多(原本程序处理10000个随机数据平均需要2.5s,现在只用0.08s)