对于以下问题,我需要更快的解决方案

时间:2020-06-07 22:36:21

标签: c++ algorithm

为您提供N对数字nk。对于每对,计算P的除数。

P = k^n * (1 + 2 + 3 + ... + k)

nr_div_huge.in中读取N,然后再读取Nn and k。在输出文件nr_div_huge.out中的每一行上,写有每对P的除数的数量。因为它可能很大,所以将以1.000.000.007的模数显示。

  • 1≤N≤15.000
  • 1≤k,n≤1.000.000.000
  • 内存限制:0.1MB
  • 时间限制:0.2秒

示例:

nr_div_huge.in

2
2 3
4 4

nr_div_huge.out

8
20

说明:

对于第一对,P = 54和54具有8个除数。 对于第二对,P = 2560和2560有20个除数。

那是我的代码:

#include <iostream>
#include <fstream>
#define MOD 1000000007
using namespace std;

ifstream fin ("nr_div_huge.in");
ofstream fout("nr_div_huge.out");

bool sieve[30000];
long long int prime[3000];
int N, k, n, r, P;

//making the Sieve of Eratosthenes to have in prime array the prime numbers up to 30000
void Erat()
{
    int r=0;
    sieve[0]=sieve[1]=1;

    for(int i=2; i*i<=30000; i++)
        if(sieve[i]==0)
            for(int j=2; j<=30000/i; j++)sieve[i*j]=1;

    for(int i=1; i<=30000; ++i)
        if(!sieve[i])prime[++r]=i;
}

//finding the numbers of divisors with Euler formula
int divisors(unsigned int n, unsigned int power)
{
    int r=0, d, p, nr=1;
    d=prime[++r];

    while(n>1)
    {
        p=0;
        while(n%d==0)
        {
            p++;
            n/=d;
        }

        if(d==2 && p>0)
            nr=(1LL*nr*(p*power))%MOD;
        else if(p>0)
            nr=(1LL*nr*(p*power+1))%MOD;

        if(prime[r+1]==0)d++;
        else d=prime[++r];

        if(d*d>n)d=n;
    }

    return nr;
}
/*
    I've transformed the formula P = k^n * (1 + 2 + 3 + ... +k) into P= k^(n+1)*(k+1)/2

    Also, I've used this property:
        If we have 2 numbers a and b, which are co-prime numbers, a will have n divisors and b m divisors,
        the a*b number will have m*n divisors.

*/
int NrP(long long int n,long long int k)
{
    unsigned long long int power=1,nrdk1,nrdkn,res=0;

    nrdk1=divisors(k+1,1);
    nrdkn=divisors(k,n+1);
    res=(1LL*nrdk1*nrdkn)%MOD;
    return res;
}


int main()
{
    Erat();
    fin>>N;

    for(int i=1; i<=N; i++)
    {
        fin>>n>>k;
        fout<<NrP(n,k)<<'\n';
    }
    return 0;
}

算法说明:

我一开始就制作了Eratosthenes筛子,以使prime数组中的质数最大为30000。为了更快,为了计算1 + 2 + 3 + ... + k的总和,我使用公式(k*(k+1))/2,P变成(k^(n+1) * (k+1))/2。由于k和k + 1是互质的,因此k ^(n + 1)和k + 1也将互质,其中之一将是偶数。为了消除/2,我将偶数相除。因为数字是互质数,所以除数为n*m,其中nk^(n+1)的除数,m是{{ 1}}。

为了计算除数,我使用了欧拉公式:

k+1

我需要一个更快的解决方案。

2 个答案:

答案 0 :(得分:1)

素数分解k。假设k2*2*2*3*5 = 120
因此,k的任何因数必须为2^x * 3^y * 5^z,其中xyz可以为[0,1,2][0,1],{{ 1}}。 同样,[0,1]的除数必须采用k^n的形式,其中2^x*3^y*5^zxy可以是z,{{1} },[0,1 .. 3*n]。 这样,您可以与这些元素形成唯一组合的数量就是所有选择的乘积:[0, .. n]

(如果不清楚,请考虑类比计算冰淇淋圣代的独特组合数,其中冰淇淋圣代由3种可能的冰淇淋口味中的1种和4种可能的冰淇淋浇头中的1个组成...这样一来,唯一的圣代总数为3 * 4。在这里,我们的选择是使用[0, .. n]个,(3*n+1)*(n+1)*(n+1)个和2个。每个独特的组合都会产生独特的产品。)

还将3的素数添加到素数计数中,它应该可以工作。

编辑:我看到您提出的解决方案已经在使用Euler函数(5)。

此处概述的解决方案也使用了该方法,但是缺少的见解是(k * (k+1)) / 2的素因数分解很容易从n = p1^e1 * p2^e2 * … * pk^ek - prime factorization的素因数分解推导:{{1}的素因数分解}是k^2,而k的素数分解是12。因此,您无需计算2^2 * 3。您只需要对12*12 = 144进行素数分解,然后使用此数据得出2^2 * 3 * 2^2 * 3 = 2^4 * 3^2的素数分解。

==编辑2 == 之后,您仍然会有错误,因此某些k^n的计算值需要是k吗?另外,我看到您的筛子上升到3000,但不应该上升到sqrt(10 ^ 9)吗?

答案 1 :(得分:1)

您必须先做一些数学运算才能使问题更简单。如果不这样做,您将结束计算数字,该数字不能放在long long变量中,这需要像gmp这样的多精度库并且要花费太多时间。

但是一旦您观察到1 + 2 + ... + k仅为k *(k + 1)/ 2,您将得到以下结果:

P = k^(n+1) * (k+1) / 2

kk+1是互素数(连续整数的平凡性质)。然后,您只需分解素数中的kk+1。其中之一可被2整除。但这即使不计算P 其主要因子分解,也能使您

从那里开始,您有P = a0^b0 * ... * ax^bx,除数为(b0+1)*...*(bx+1)

示例应用:

  • 2,3:P = 3 ^ 3 * 4/2 = 2 ^ 1 * 3 ^ 3-除数2 * 4 = 8
  • 4,4:P = 4 ^ 5 * 5/2 = 5 *(2 ^ 2)^ 5/2 = 2 ^ 9 * 5 ^ 1-除数的数量10 * 2 = 20

您仍然必须用C ++编写算法,但是它比大量运算要有效得多。


这是可能的C ++实现

#include <vector>
#include <iostream>
#include <fstream>

// Will search all primes up to (SQSIZE * SQSIZE) - 1
constexpr const unsigned long SQSIZE = 200;

std::vector<unsigned> make_sieve(unsigned sqsize) {
    unsigned size = sqsize * sqsize;
    std::vector<unsigned> sieve(size, 0);
    // Classic Eratosthene
    for (unsigned i=2; i<sqsize; i++) {
        if(sieve[i] == 0) {
            for(unsigned j = i*i; j<size; j+=i) {
                sieve[j] = 1;
            }
        }
    }
    unsigned len=0;
    // Pack prime number in the vector
    for (unsigned i=2; i<size; i++) {
        if (sieve[i] == 0) sieve[len++] = i;
    }
    sieve.resize(len);
    return sieve;
}

unsigned long n_divisors(unsigned long k, unsigned long n, const std::vector<unsigned> &sieve) {
    unsigned long n_div = 1;
    for(unsigned i: sieve) {
        unsigned exp = 0;
        while ((k % i) == 0) {
            exp += 1;
            k /= i;
        }
        // special processing if k divisible by 2: use k^n/2
        if ((i == 2) && (exp != 0)) n_div *= (exp * n - 1) + 1;
        else n_div *= exp * n + 1;
        if (k == 1) break;
    }
    if (n_div == 1) n_div = 2;  // k is prime and greater sieve max
    return n_div;
}

int main() {
    std::vector<unsigned> sieve = make_sieve(SQSIZE);
    unsigned long k, n, n_div;
    unsigned N;

    std::ifstream fin ("nr_div_huge.in");
    std::ofstream fout("nr_div_huge.out");

    fin >> N;
    for(unsigned i=0; i<N; i++) {
        fin >> n >> k;
        n_div = n_divisors(k, n+1, sieve) * n_divisors(k + 1, 1, sieve);
        fout << n_div << '\n';
    }
    return 0;
}

这里不需要long longnk小于1000000000,并且适合32位整数。因此,使用了unsigned long

当心:上面的代码盲目地假设没有IO问题,并且从不对其进行测试。绝对不要为生产级代码这样做...