PTA Factorial Factors

原题

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
9

Sample 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即为答案。

AC代码
#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)

你可能感兴趣的:(ACM训练)