为什么此模块化GCD在大量输入时会失败?

时间:2018-08-11 17:02:54

标签: c++ algorithm modular-arithmetic

这个问题实际上来自一个名为codechef的竞争编程网站。

问题如下。

  

给出整数A,B和N,您应该计算A ^ N + B ^ N的GCD和   | AB |。 (假设对于任何正整数a,GCD(0,a)= a)。从此   数字可能非常大,以1000000007(109 + 7)为模进行计算。

完整的问题是here

值约束如下:

  

1 <= A,B,N <= 1e9(子任务#1)

     

1 <= A,B,N <= 1e12(子任务#2)

     

B <=总是A

我的解决方案通过了第一个子任务,但是当A,B,N的值很大时,第二次失败。

这是我的解决方案:

#include <bitset>
#include <iostream>

using std::bitset;
using std::cin;
using std::cout;

typedef unsigned long long ull;
constexpr size_t bit_size = sizeof(ull) * 8;

ull modular_exponetiation(ull a, bitset<bit_size> n, ull mod) {
  ull c = 0;
  ull d = 1;
  for (int t = n.size() - 1; t >= 0; t--) {
    c *= 2;
    d = ((d % mod) * (d % mod)) % mod;  //(d*d)%mod
    if (n[t] == 1) {
      c++;
      d = ((d % mod) * (a % mod)) % mod;  //(d*a)%mod
    }
  }
  return d;
}

ull euclid_gcd(ull a, ull b) {
  if (b == 0)
    return a;
  else
    return euclid_gcd(b, a % b);
}

int main() {
  int test;
  cin >> test;
  while (test--) {
    ull a, b, n;
    cin >> a >> b >> n;
    ull modder = a - b;
    if (modder != 0) {
      ull out_res = 0;
      bitset<bit_size> bin_rip(n);
      ull first_mod_res = (modular_exponetiation(a, bin_rip, modder) +
                           modular_exponetiation(b, bin_rip, modder)) %
                          modder;
      if (first_mod_res == 0)
        out_res = modder;
      else
        out_res = euclid_gcd(modder, first_mod_res);
      cout << out_res % 1000000007 << std::endl;
    } else {
      // mod by 0 is not defined using the problem defined result.
      // GCD(0,a) == a;
      ull modder = 1000000007;
      bitset<bit_size> bin_rip(n);
      ull res = (modular_exponetiation(a, bin_rip, modder) +
                 modular_exponetiation(b, bin_rip, modder)) %
                modder;
      cout << res << std::endl;
    }
  }
  return 0;
}

请求

这不是家庭作业,我也不想要精确的答案或代码更正。我确实理解所有这些内容,但不明白为什么在更大的值上它会失败?

任何方向或提示都会有用。

2 个答案:

答案 0 :(得分:3)

如果modder = 1e12,则您的取模不起作用。原因1e12 * 1e12。会有溢出。

查看this以进行模运算而不会溢出。

您可以尝试一下。这里的乘法是通过求和完成的。

long long multiply(long long a,long long b,long long m){
if(b == 0){
    return 0;
}
if(b==1){
    return a%m;
}
if(b&1){
    return ((a%m)+multiply(a,b-1,m))%m;
}
long long x = multiply(a,b>>1,m);
return multiply(x,2,m);
}

long long bigmod(long long a,long long b, long long m){
if(b == 0){
    return 1;
}
if(b == 1){
    return a%m;
}
if(b & 1){
    return multiply(a%m,bigmod(a,b-1,m),m);
}
long long x = bigmod(a,b>>1,m);
return multiply(x,x,m);
}

答案 1 :(得分:1)

特别感谢萨吉布指出问题。

在这里,我会自己回答,以便更好地理解。

d = ((d % mod) * (d % mod)) % mod; 
d = ((d % mod) * (a % mod)) % mod;

是这两行中断并导致溢出。所有积分都归@sajib所有。

他还指出了解决此问题的正确方法(与求和)。我很高兴了解他的代码的作用。


所以我在这里详细解释。

(a * b) % m
如果ab都是非常长的long long值,则

将导致溢出。

幼稚的方法是使用任意精度的数据类型,例如python中的int或Java中的Biginteger类。但是,这种方法不会奏效,因为将字符串内部转换为int然后执行操作将导致二进制数系统中加法和乘法的计算变慢。

有效的解决方案:由于a和b可能非常大,所以如果我们尝试直接相乘,则肯定会溢出。因此,我们使用乘法的基本方法,即

a * b = a + a + … + a (b times)

因此我们可以轻松地计算加法的值(模m下),而无需任何 计算中溢出。但是,如果我们尝试将a的值重复加到b倍,那么对于大的b值肯定会超时,因为这种方法的时间复杂度将变为O(b)。

因此,我们以更简单的方式将a的上述步骤分开,即

If b is even then 
  a * b = 2 * a * (b / 2), 
otherwise 
  a * b = a + a * (b - 1)

实现如下:

// Returns (a * b) % mod
long long moduloMultiplication(long long a,
                               long long b,
                               long long mod)
{
    long long res = 0;  // Initialize result

    // Update a if it is more than
    // or equal to mod
    a %= mod;

    while (b)
    {
        // If b is odd, add a with result
        if (b & 1)
            res = (res + a) % mod;

        // Here we assume that doing 2*a
        // doesn't cause overflow
        a = (2 * a) % mod;

        b >>= 1;  // b = b / 2
    }

    return res;
}