ACM-分解素因子

在一些数学题目中,经常需要将某个数进行分解,以便于发现一些规律然后进行求解。

首先不得不说的一个定理就是唯一分解定理:任何一个大于1的自然数n都可以唯一分解成有限个质数的乘积,n=p1^a1 * p2^a2 * ... * pn^a3,其中p1<p2<...<pn均为质数,它们的指数均为正整数。

这个定理的一些性质可以参考百度百科(点击打开链接)。

这样一来,利用唯一分解定理就可以将任何整数分解成统一格式的一个表达式,这是非常有用的,因为当我们比较两个数的某些特性的时候,统一的格式总是能省去很多麻烦的步骤。

我这里的素因子分解算法有两种,一种就是普通的遍历判断,还有一种是使用随机算法实现,据说可以算大数的因子,同时还可以判断素数,效率很高!下面给出两种算法的实现,具体采用哪种就看情况吧!

1、简单筛选

int fac[1000005];
int cnt = 0;
void factor(int n)
{
    int a = 1;
    for(int i=2; i*i<=n; i+=a,a=2)
    {
        if(n%i==0) while(n%i==0)
        {
            fac[cnt++] = i;
            n /= i;
        }
    }
    if(n > 1)
        fac[cnt++] = n;
}


2、随机算法

// Miller_Rabin算法进行素数测试
// 速度快,而且可以判断 <2^63的数
const int S = 20;  //随机算法判定次数,S越大,判错概率越小
// 计算(a*b)%c,a,b都是long long的数,直接相乘可能溢出的
// a,b,c<2^63
long long mult_mod(long long a, long long b, long long c)
{
    a %= c;
    b %= c;
    long long ret = 0;
    while(b)
    {
        if(b & 1)
            {ret+=a; ret%=c;}
        a <<= 1;
        if(a >= c) a%=c;
        b >>= 1;
    }
    return ret;
}

// 计算x^n%c
long long pow_mod(long long x, long long n, long long mod)
{
    if(n == 1) return x%mod;
    x %= mod;
    long long tmp = x;
    long long ret = 1;
    while(n)
    {
        if(n & 1)
            ret = mult_mod(ret,tmp,mod);
        tmp = mult_mod(tmp,tmp,mod);
        n >>= 1;
    }
    return ret;
}

// 以a为基,n-1=x*2^t,a^(n-1)=1(mod n),验证n是不是合数
// 一定是合数返回true,不一定返回false
bool check(long long a, long long n, long long x, long long t)
{
    long long ret = pow_mod(a,x,n);
    long long last=ret;
    for(int i=1; i<=t; ++i)
    {
        ret = mult_mod(ret,ret,n);
        if(ret==1 && last!=1 && last!=n-1)
            return true;  // 合数
        last = ret;
    }
    if(ret != 1) return true;
    return false;
}

// Miller_Rabin()算法素数判定
// 是素数返回true.(可能是伪素数,但概率极小)
// 合数返回false;
bool Miller_Rabin(long long n)
{
    if(n <  2) return false;
    if(n == 2) return true;
    if((n&1) == 0) return false;  // 偶数
    long long x = n-1;
    long long t = 0;
    while((x&1) ==0) {x>>=1; t++;}
    for(int i=0; i<S; ++i)
    {
        long long a = rand()%(n-1)+1;  // rand()需要stdlib.h头文件
        if(check(a,n,x,t))
            return false;  // 合数
    }
    return true;
}

//pollard_rho算法进行质因数分解
long long factor[1000005];  // 质因数分解结果(刚返回时是无序的)
int tol;                // 质因数的个数。数组小标从0开始
long long MAXFAC;

long long gcd(long long a, long long b)
{
    if(a == 0) return 1;
    if(a <  0) return gcd(-a,b);
    while(b)
    {
        long long t = a%b;
        a = b;
        b = t;
    }
    return a;
}

long long Pollard_rho(long long x, long long c)
{
    long long i =1, k=2;
    long long x0=rand()%x;
    long long y = x0;
    while(1)
    {
        i++;
        x0 = (mult_mod(x0,x0,x)+c)%x;
        long long d = gcd(y-x0,x);
        if(d!=1 && d!=x) return d;
        if(y == x0) return x;
        if(i == k) {y=x0; k+=k;}
    }
}

//对n进行素因子分解
void findfac(long long n)
{
    if(Miller_Rabin(n))  // 素数
    {
        factor[tol++] = n;
        return;
    }
    long long p = n;
    while(p >= n)
        p = Pollard_rho(p,rand()%(n-1)+1);
    findfac(p);
    findfac(n/p);
}


int main()
{
    //srand(time(NULL));//需要time.h头文件//POJ上G++不能加这句话
    long long n;
    while(scanf("%I64d",&n) != EOF)
    {
        tol = 0;
        findfac(n);
        for(int i=0;i<tol;i++)printf("%I64d ",factor[i]);
        printf("\n");
        if(Miller_Rabin(n))printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}


以题为例,HDOJ:4139,时空转移(点击打开链接),题目如下:

Big Division

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 402    Accepted Submission(s): 149


Problem Description
A theoretical physicist friend is doing research about the "Answer to the Ultimate Question of Life, the Universe, and Everything", he thinks that it is not 42 as suggested by “The Hitchhiker's Guide to the Galaxy” science fiction comedy series; instead he thinks it is the result of dividing the products of two sequences of positive integers A and B!
The task of calculating the product of A and B followed by division turned out to be not as easy as it looks, specially with the sequences being long and the products getting too large very quickly! Even using a modern computer, a straight forward implementation for the calculations may take very long time to complete! And this is where we seek your help as a brilliant computer scientist!
 

Input
The first line of input contains an integer (1 <= T <= 200), the number of test cases.
T test cases follow, the first line of each test case contains two integers (1 <= N, M<= 110,000), the lengths of sequences A and B respectively. Two lines follow, the first line contains N space separated integers (0 < A0, A1 … An <= 1,000,000), and the second line contains M space separated integers (0 < B0, B1 … Bm <= 1,000,000).
 

Output
For each test case, print one line containing the result of dividing the product of sequence A by the product of sequence B as a reduced fraction of the format “X / Y” (Notice the single space before and after the fraction sign). X and Y are guaranteed to fit in 32-bit signed integer. A reduced fraction is a fraction such that the greatest common divisor between the nominator and the denominator is 1.
 

Sample Input
  
  
2 3 1 2 4 5 12 2 4 1 15 5 1 7 2
 

Sample Output
  
  
Case #1: 10 / 3 Case #2: 3 / 14
 

题意:
题目的意思比较简单,就是输入两行数,分别以其乘积作为分母和分子,输出化约分后的答案。
分析:
约分无非就是去掉共同的因子,所以问题的关键就是如何求出它们共同的因子,唯一分解定理正是我们最好的选择。那么最后的问题就是如何记录这些因子,最便于模拟约分的效果,答案是哈希因子,将因子映射到数组下标,数组值则记录该因子的出现次数,分母有则减一,分子有则加一。最后遍历一次数组,算出乘积即可。
原代码
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

int fac[1000005];

void factor(int n, int f)
{
    int a = 1;
    for(int i=2; i*i<=n; i+=a,a=2)
    {
        if(n%i==0) while(n%i==0)
        {
            fac[i] += f;  // 映射因子,便于操作共同因子
            n /= i;
        }
    }
    if(n > 1)
        fac[n] += f;
}

int main()
{//freopen("sample.txt", "r", stdin);
    int cas, t=0, maxs=0;
    scanf("%d", &cas);
    while(cas--)
    {
        int n, m, tmp;
        memset(fac, 0, sizeof(fac));
        scanf("%d%d", &n, &m);
        for(int i=0; i<n; ++i)
        {
            scanf("%d", &tmp);
            factor(tmp, 1);
            maxs = max(maxs, tmp);
        }
        for(int i=0; i<m; ++i)
        {
            scanf("%d", &tmp);
            factor(tmp, -1);
            maxs = max(maxs, tmp);
        }
        int ans1=1, ans2=1;
        for(int i=2; i<=maxs; ++i)
        {
            if(fac[i] > 0)
                ans1 *= pow(1.0*i, fac[i]);
            else if(fac[i] < 0)
                ans2 *= pow(1.0*i, -fac[i]);
        }
        printf("Case #%d: %d / %d\n", ++t, ans1, ans2);
    }
    return 0;
}

其它类似的题目还有,HDOJ:1299、4228。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值