Как перечислить x ^ 2 + y ^ 2 = z ^ 2 - 1 (с дополнительными ограничениями)

Позволяет N быть числом (10<=N<=10^5).

Я должен разбить его на 3 числа (x,y,z), чтобы он удовлетворял следующим условиям.

1. x<=y<=z 
2. x^2+y^2=z^2-1;
3. x+y+z<=N

Я должен найти, сколько комбинаций я могу получить из данных чисел в методе.

Я попытался следующим образом, но это заняло так много времени для большего числа и в результате тайм-аут..

int N= Int32.Parse(Console.ReadLine());
List<String> res = new List<string>();

//x<=y<=z
int mxSqrt = N - 2;
int a = 0, b = 0;
for (int z = 1; z <= mxSqrt; z++)
{
    a = z * z;
    for (int y = 1; y <= z; y++)
    {
        b = y * y;
        for (int x = 1; x <= y; x++)
        {
            int x1 = b + x * x;
            int y1 = a - 1;
            if (x1 == y1 && ((x + y + z) <= N))
            {
                res.Add(x + "," + y + "," + z);
            }
        }
    }
}
Console.WriteLine(res.Count());

Мой вопрос:

Мое решение требует времени для большего числа (я думаю это для циклов), как я могу улучшить это?

Есть ли лучший подход к тому же?

Ответы

Ответ 1

Здесь метод, который перечисляет тройки, а не исчерпывающе проверяет их, используя теорию чисел, как описано здесь: https://mathoverflow.net/questions/29644/enumerating-ways-to-decompose-an-integer-into-the- сумма-из-двух квадратов

Поскольку математика заняла у меня некоторое время, чтобы осмыслить, и некоторое время, чтобы реализовать (собрав некоторый код, который заимствован над ней), и так как я не чувствую большой авторитет в этом вопросе, я предоставлю читателю возможность исследовать. Это основано на выражении чисел как гауссовых целочисленных сопряженных. (a + bi)*(a - bi) = a^2 + b^2. Сначала мы делим число z^2 - 1 на простые числа, разлагаем простые числа на гауссовы сопряженные и находим различные выражения, которые мы расширяем и упрощаем, чтобы получить a + bi, которое затем можно повысить, a^2 + b^2.

Приятное чтение о функции суммы квадратов обнаруживает, что мы можем исключить любого кандидата z^2 - 1 который содержит простое число формы 4k + 3 с нечетной степенью. Используя только эту проверку, я смог уменьшить цикл Чернослива на 10 ^ 5 с 214 секунд до 19 секунд (на repl.it), используя приведенный ниже код простого фактора Розетты.

Реализация здесь просто демонстрация. У него нет обработки или оптимизации для ограничения x и y. Скорее, он просто перечисляет по ходу дела. Играть с этим здесь.

Код Python:

# https://math.stackexchange.com/questions/5877/efficiently-finding-two-squares-which-sum-to-a-prime
def mods(a, n):
    if n <= 0:
        return "negative modulus"
    a = a % n
    if (2 * a > n):
        a -= n
    return a

def powmods(a, r, n):
    out = 1
    while r > 0:
        if (r % 2) == 1:
            r -= 1
            out = mods(out * a, n)
        r /= 2
        a = mods(a * a, n)
    return out

def quos(a, n):
    if n <= 0:
        return "negative modulus"
    return (a - mods(a, n))/n

def grem(w, z):
    # remainder in Gaussian integers when dividing w by z
    (w0, w1) = w
    (z0, z1) = z
    n = z0 * z0 + z1 * z1
    if n == 0:
        return "division by zero"
    u0 = quos(w0 * z0 + w1 * z1, n)
    u1 = quos(w1 * z0 - w0 * z1, n)
    return(w0 - z0 * u0 + z1 * u1,
           w1 - z0 * u1 - z1 * u0)

def ggcd(w, z):
    while z != (0,0):
        w, z = z, grem(w, z)
    return w

def root4(p):
    # 4th root of 1 modulo p
    if p <= 1:
        return "too small"
    if (p % 4) != 1:
        return "not congruent to 1"
    k = p/4
    j = 2
    while True:
        a = powmods(j, k, p)
        b = mods(a * a, p)
        if b == -1:
            return a
        if b != 1:
            return "not prime"
        j += 1

def sq2(p):
    if p % 4 != 1:
      return "not congruent to 1 modulo 4"
    a = root4(p)
    return ggcd((p,0),(a,1))

# https://rosettacode.org/wiki/Prime_decomposition#Python:_Using_floating_point
from math import floor, sqrt

def fac(n):
    step = lambda x: 1 + (x<<2) - ((x>>1)<<1)
    maxq = long(floor(sqrt(n)))
    d = 1
    q = n % 2 == 0 and 2 or 3 
    while q <= maxq and n % q != 0:
        q = step(d)
        d += 1
    return q <= maxq and [q] + fac(n//q) or [n]

# My code...
# An answer for  https://stackoverflow.com/questions/54110614/

from collections import Counter
from itertools import product
from sympy import I, expand, Add

def valid(ps):
  for (p, e) in ps.items():
    if (p % 4 == 3) and (e & 1):
      return False
  return True

def get_sq2(p, e):
  if p == 2:
    if e & 1:
      return [2**(e / 2), 2**(e / 2)]
    else:
      return [2**(e / 2), 0]
  elif p % 4 == 3:
    return [p, 0]
  else:
    a,b = sq2(p)
    return [abs(a), abs(b)]

def get_terms(cs, e):
  if e == 1:
    return [Add(cs[0], cs[1] * I)]
  res = [Add(cs[0], cs[1] * I)**e]
  for t in xrange(1, e / 2 + 1):
    res.append(
      Add(cs[0] + cs[1]*I)**(e-t) * Add(cs[0] - cs[1]*I)**t)
  return res

def get_lists(ps):
  items = ps.items()
  lists = []
  for (p, e) in items:
    if p == 2:
      a,b = get_sq2(2, e)
      lists.append([Add(a, b*I)])
    elif p % 4 == 3:
      a,b = get_sq2(p, e)
      lists.append([Add(a, b*I)**(e / 2)])
    else:
      lists.append(get_terms(get_sq2(p, e), e))
  return lists


def f(n):
  for z in xrange(2, n / 2):
    zz = (z + 1) * (z - 1)
    ps = Counter(fac(zz))
    is_valid = valid(ps)
    if is_valid:
      print "valid (does not contain a prime of form\n4k + 3 with an odd power)"
      print "z: %s, primes: %s" % (z, dict(ps))
      lists = get_lists(ps)
      cartesian = product(*lists)
      for element in cartesian:
        print "prime square decomposition: %s" % list(element)
        p = 1
        for item in element:
          p *= item
        print "complex conjugates: %s" % p
        vals = p.expand(complex=True, evaluate=True).as_coefficients_dict().values()
        x, y = vals[0], vals[1] if len(vals) > 1 else 0
        print "x, y, z: %s, %s, %s" % (x, y, z)
        print "x^2 + y^2, z^2-1: %s, %s" % (x**2 + y**2, z**2 - 1)
      print ''

if __name__ == "__main__":
  print f(100)

Выход:

valid (does not contain a prime of form
4k + 3 with an odd power)
z: 3, primes: {2: 3}
prime square decomposition: [2 + 2*I]
complex conjugates: 2 + 2*I
x, y, z: 2, 2, 3
x^2 + y^2, z^2-1: 8, 8

valid (does not contain a prime of form
4k + 3 with an odd power)
z: 9, primes: {2: 4, 5: 1}
prime square decomposition: [4, 2 + I]
complex conjugates: 8 + 4*I
x, y, z: 8, 4, 9
x^2 + y^2, z^2-1: 80, 80

valid (does not contain a prime of form
4k + 3 with an odd power)
z: 17, primes: {2: 5, 3: 2}
prime square decomposition: [4 + 4*I, 3]
complex conjugates: 12 + 12*I
x, y, z: 12, 12, 17
x^2 + y^2, z^2-1: 288, 288

valid (does not contain a prime of form
4k + 3 with an odd power)
z: 19, primes: {2: 3, 3: 2, 5: 1}
prime square decomposition: [2 + 2*I, 3, 2 + I]
complex conjugates: (2 + I)*(6 + 6*I)
x, y, z: 6, 18, 19
x^2 + y^2, z^2-1: 360, 360

valid (does not contain a prime of form
4k + 3 with an odd power)
z: 33, primes: {17: 1, 2: 6}
prime square decomposition: [4 + I, 8]
complex conjugates: 32 + 8*I
x, y, z: 32, 8, 33
x^2 + y^2, z^2-1: 1088, 1088

valid (does not contain a prime of form
4k + 3 with an odd power)
z: 35, primes: {17: 1, 2: 3, 3: 2}
prime square decomposition: [4 + I, 2 + 2*I, 3]
complex conjugates: 3*(2 + 2*I)*(4 + I)
x, y, z: 18, 30, 35
x^2 + y^2, z^2-1: 1224, 1224

Ответ 2

Вот простое улучшение в Python (преобразование в более быстрый эквивалент в C-коде оставлено в качестве упражнения для читателя). Чтобы получить точные сроки вычислений, я удалил печать самих решений (после проверки их в предыдущем запуске).

  • Используйте внешний цикл для одной свободной переменной (я выбрал z), ограниченный только ее отношением к N
  • Используйте внутренний цикл (я выбрал y), ограниченный индексом внешнего цикла.
  • Третья переменная вычисляется напрямую в соответствии с требованием 2.

Сроки результаты:

-------------------- 10 
 1 solutions found in 2.3365020751953125e-05  sec.
-------------------- 100 
 6 solutions found in 0.00040078163146972656  sec.
-------------------- 1000 
 55 solutions found in 0.030081748962402344  sec.
-------------------- 10000 
 543 solutions found in 2.2078349590301514  sec.
-------------------- 100000 
 5512 solutions found in 214.93411707878113  sec.

Это 3:35 для большого случая, плюс ваше время, чтобы сопоставить и/или распечатать результаты.

Если вам нужен более быстрый код (это все еще довольно грубая сила), изучите диофантовы уравнения и параметризацию для генерации (y, x) пар, учитывая целевое значение z^2 - 1.

import math
import time

def break3(N):
    """
    10 <= N <= 10^5
    return x, y, z triples such that:
        x <= y <= z
        x^2 + y^2 = z^2 - 1        
        x + y + z <= N
    """

    """
    Observations:
    z <= x + y
    z < N/2
    """

    count = 0
    z_limit = N // 2
    for z in range(3, z_limit):

        # Since y >= x, there a lower bound on y
        target = z*z - 1
        ymin = int(math.sqrt(target/2))
        for y in range(ymin, z):
            # Given y and z, compute x.
            # That a solution iff x is integer.
            x_target = target - y*y
            x = int(math.sqrt(x_target))
            if x*x == x_target and x+y+z <= N:
                # print("solution", x, y, z)
                count += 1

    return count


test = [10, 100, 1000, 10**4, 10**5]
border = "-"*20

for case in test: 
    print(border, case)
    start = time.time()
    print(break3(case), "solutions found in", time.time() - start, "sec.")

Ответ 3

Нет времени, чтобы правильно его протестировать, но, похоже, он дал те же результаты, что и ваш код (при 100 → 6 результатов и при 1000 → 55 результатов).

С N=1000 время 2ms против ваших 144ms также без списка

и N=10000 время 28ms

var N = 1000;
var c = 0;

for (int x = 2; x < N; x+=2)
{
    for (int y = x; y < (N - x); y+=2)
    {
        long z2 = x * x + y * y + 1;
        int z = (int) Math.Sqrt(z2);
        if (x + y + z > N)
            break;
        if (z * z == z2)
            c++;
    }
}

Console.WriteLine(c);

Ответ 4

Границы x и y являются важной частью проблемы. Я лично пошел с этим запросом Wolfram Alpha и проверил точные формы переменных.

Благодаря @Bleep-Bloop и комментариям, была найдена очень элегантная оптимизация границ: x < n и x <= y < n - x. Результаты одинаковы, а времена почти идентичны.

Кроме того, поскольку единственными возможными значениями x и y являются положительные четные целые числа, мы можем уменьшить количество итераций цикла вдвое.

Для дальнейшей оптимизации, так как мы вычисляем верхнюю границу x, мы строим список всех возможных значений для x и проводим параллельные вычисления. Это экономит огромное количество времени при более высоких значениях N но немного медленнее при меньших значениях из-за издержек распараллеливания.

Вот окончательный код:

Непараллельная версия, со значениями int :

List<string> res = new List<string>();
int n2 = n * n;

double maxX = 0.5 * (2.0 * n - Math.Sqrt(2) * Math.Sqrt(n2 + 1));

for (int x = 2; x < maxX; x += 2)
{
    int maxY = (int)Math.Floor((n2 - 2.0 * n * x - 1.0) / (2.0 * n - 2.0 * x));

    for (int y = x; y <= maxY; y += 2)
    {
        int z2 = x * x + y * y + 1;
        int z = (int)Math.Sqrt(z2);

        if (z * z == z2 && x + y + z <= n)
            res.Add(x + "," + y + "," + z);
    }
}

Параллельная версия с long значениями:

using System.Linq;

...

// Use ConcurrentBag for thread safety
ConcurrentBag<string> res = new ConcurrentBag<string>();
long n2 = n * n;

double maxX = 0.5 * (2.0 * n - Math.Sqrt(2) * Math.Sqrt(n2 + 1L));

// Build list to parallelize
int nbX = Convert.ToInt32(maxX);
List<int> xList = new List<int>();
for (int x = 2; x < maxX; x += 2)
    xList.Add(x);

Parallel.ForEach(xList, x =>
{
    int maxY = (int)Math.Floor((n2 - 2.0 * n * x - 1.0) / (2.0 * n - 2.0 * x));

    for (long y = x; y <= maxY; y += 2)
    {
        long z2 = x * x + y * y + 1L;
        long z = (long)Math.Sqrt(z2);

        if (z * z == z2 && x + y + z <= n)
            res.Add(x + "," + y + "," + z);
    }
});

Когда запускается индивидуально на процессоре i5-8400, я получаю следующие результаты:

N: 10; Решения: 1; Прошедшее время: 0,03 мс (не параллельно, int)

N: 100; Решения: 6; Прошедшее время: 0,05 мс (не параллельно, int)

N: 1000; Решения: 55; Прошедшее время: 0,3 мс (не параллельно, int)

N: 10000; Решения: 543; Прошедшее время: 13,1 мс (не параллельно, int)

N: 100000; Решения: 5512; Прошедшее время: 849,4 мс (параллельное, long)


Вы должны использовать long когда N больше 36340, потому что когда оно возведено в квадрат, оно переполняет значение int max. Наконец, параллельная версия начинает становиться лучше, чем простая, когда N составляет около 23000, с int s.

Ответ 5

#include<iostream>
#include<math.h>
int main()
{
    int N = 10000;
    int c = 0;
    for (int x = 2; x < N; x+=2)
    {
        for (int y = x; y < (N - x); y+=2)
        {
            auto z = sqrt(x * x + y * y + 1);
            if(x+y+z>N){
                break;
            }
            if (z - (int) z == 0)
            {
                c++;
            }
        }
    }
    std::cout<<c;
}

Это моё решение. При тестировании предыдущих решений этой проблемы я обнаружил, что x, y всегда четные, а z нечетные. Я не знаю математической природы, стоящей за этим, я сейчас пытаюсь понять это.

Ответ 6

Я хочу, чтобы это было сделано в С#, и оно должно охватывать все контрольные примеры, основанные на условии, указанном в вопросе.

Базовый код, преобразованный в long для обработки верхнего предела N <= 100000, с каждой добавленной оптимизацией, которую я мог. Я использовал альтернативные формы из @Mat (+1) Alpha-запроса Wolfram для максимально возможного предварительного вычисления. Я также провел минимальный тест идеального квадрата, чтобы избежать миллионов вызовов sqrt() на верхнем пределе:

public static void Main()
{
    int c = 0;

    long N = long.Parse(Console.ReadLine());
    long N_squared = N * N;

    double half_N_squared = N_squared / 2.0 - 0.5;
    double x_limit = N - Math.Sqrt(2) / 2.0 * Math.Sqrt(N_squared + 1);

    for (long x = 2; x < x_limit; x += 2)
    {
        long x_squared = x * x + 1;

        double y_limit = (half_N_squared - N * x) / (N - x);

        for (long y = x; y < y_limit; y += 2)
        {
            long z_squared = x_squared + y * y;
            int digit = (int) z_squared % 10;

            if (digit == 3 || digit == 7)
            {
                continue;  // minimalist non-perfect square elimination
            }

            long z = (long) Math.Sqrt(z_squared);

            if (z * z == z_squared)
            {
                c++;
            }
        }
    }

    Console.WriteLine(c);
}

Я следовал за трендом и оставил "вырожденное решение", как подразумевается в коде OP, но явно не указано.