Para dois números inteiros A e B, encontre um par de números X e Y tal que A = X * Y e B = X xor Y


22

Estou enfrentando esse problema que encontrei em um livro de programação competitivo, mas sem uma solução sobre como fazê-lo.

Para dois números inteiros A e B (pode caber no tipo inteiro de 64 bits), onde A é ímpar, encontre um par de números X e Y de modo que A = X * Y e B = X x ou Y. Minha abordagem foi listar todos os divisores de um e tente emparelhar números sob sqrt (a) com números sobre sqrt (a) que se multiplicam até um e ver se sua xor é igual a B . Mas não sei se isso é eficiente o suficiente. Qual seria uma boa solução / algoritmo para esse problema?


11
É estranho misturar um operador inteiro e um operador bit a bit. É mesmo X*You X&Y?
Eric Duminil

É multiplicação. (*)
Aster W.

Você já escreveu alguma linha de código para resolver esta tarefa? Qual linguagem de programação você pretende usar?
Lynx 242

Respostas:


5

Aqui está uma recursão simples que observa as regras que conhecemos: (1) os bits menos significativos de X e Y são definidos, pois apenas multiplicandos ímpares produzem um múltiplo ímpar; (2) se definirmos X para ter o bit mais alto de B, Y não poderá ser maior que sqrt (A); e (3) definir bits em X ou Y de acordo com o bit atual em B.

O código Python a seguir resultou em menos de 300 iterações para todos, exceto um dos pares aleatórios que escolhi do código de exemplo de Matt Timmermans . Mas o primeiro fez 231.199 iterações :)

from math import sqrt

def f(A, B):
  i = 64
  while not ((1<<i) & B):
    i = i - 1
  X = 1 | (1 << i)

  sqrtA = int(sqrt(A))

  j = 64
  while not ((1<<j) & sqrtA):
    j = j - 1

  if (j > i):
    i = j + 1

  memo = {"it": 0, "stop": False, "solution": []}

  def g(b, x, y):
    memo["it"] = memo["it"] + 1
    if memo["stop"]:
      return []

    if y > sqrtA or y * x > A:
      return []

    if b == 0:
      if x * y == A:
        memo["solution"].append((x, y))
        memo["stop"] = True
        return [(x, y)]
      else:
        return []

    bit = 1 << b

    if B & bit:
      return g(b - 1, x, y | bit) + g(b - 1, x | bit, y)
    else:
      return g(b - 1, x | bit, y | bit) + g(b - 1, x, y)

  g(i - 1, X, 1)
  return memo

vals = [
  (6872997084689100999, 2637233646), # 1048 checks with Matt's code
  (3461781732514363153, 262193934464), # 8756 checks with Matt's code
  (931590259044275343, 5343859294), # 4628 checks with Matt's code
  (2390503072583010999, 22219728382), # 5188 checks with Matt's code
  (412975927819062465, 9399702487040), # 8324 checks with Matt's code
  (9105477787064988985, 211755297373604352), # 3204 checks with Matt's code
  (4978113409908739575,67966612030), # 5232 checks with Matt's code
  (6175356111962773143,1264664368613886), # 3756 checks with Matt's code
  (648518352783802375, 6) # B smaller than sqrt(A)
]

for A, B in vals:
  memo = f(A, B)
  [(x, y)] = memo["solution"]
  print "x, y: %s, %s" % (x, y)
  print "A:   %s" % A
  print "x*y: %s" % (x * y)
  print "B:   %s" % B
  print "x^y: %s" % (x ^ y)
  print "%s iterations" % memo["it"]
  print ""

Resultado:

x, y: 4251585939, 1616572541
A:   6872997084689100999
x*y: 6872997084689100999
B:   2637233646
x^y: 2637233646
231199 iterations

x, y: 262180735447, 13203799
A:   3461781732514363153
x*y: 3461781732514363153
B:   262193934464
x^y: 262193934464
73 iterations

x, y: 5171068311, 180154313
A:   931590259044275343
x*y: 931590259044275343
B:   5343859294
x^y: 5343859294
257 iterations

x, y: 22180179939, 107776541
A:   2390503072583010999
x*y: 2390503072583010999
B:   22219728382
x^y: 22219728382
67 iterations

x, y: 9399702465439, 43935
A:   412975927819062465
x*y: 412975927819062465
B:   9399702487040
x^y: 9399702487040
85 iterations

x, y: 211755297373604395, 43
A:   9105477787064988985
x*y: 9105477787064988985
B:   211755297373604352
x^y: 211755297373604352
113 iterations

x, y: 68039759325, 73164771
A:   4978113409908739575
x*y: 4978113409908739575
B:   67966612030
x^y: 67966612030
69 iterations

x, y: 1264664368618221, 4883
A:   6175356111962773143
x*y: 6175356111962773143
B:   1264664368613886
x^y: 1264664368613886
99 iterations

x, y: 805306375, 805306369
A:   648518352783802375
x*y: 648518352783802375
B:   6
x^y: 6
59 iterations

Isso não funciona quando B <sqrt (A), por exemplo, quando X == Y
Matt Timmermans

X == Y é apenas o exemplo mais simples. B pode ser qualquer número <sqrt (A), como X = 0x30000001, Y = 0x30000007, A = X * Y, B = 6
Matt Timmermans

@MattTimmermans great catch. Adicionei manipulação e seu exemplo aos testes, que são resolvidos em 59 iterações. Informe-me se você encontrar outros problemas (ou se esse problema parecer não resolvido).
גלעד ברקן

Interessante. Eu esperava que isso fosse caro quando você conseguiu. Sabemos que existem casos caros do 231199, mas está sendo difícil caracterizá-los. De qualquer forma, parece que isso funciona bem agora.
Matt Timmermans

9

Você sabe que pelo menos um fator é <= sqrt (A). Vamos fazer aquele X.

O comprimento de X em bits será cerca de metade do comprimento de A.

Os bits superiores de X, portanto - aqueles com valor superior a sqrt (A) - são todos 0 e os bits correspondentes em B devem ter o mesmo valor que os bits correspondentes em Y.

Conhecer os bits superiores de Y fornece um intervalo bastante pequeno para o fator correspondente X = A / Y. Calcule Xmin e Xmax correspondentes aos maiores e menores valores possíveis para Y, respectivamente. Lembre-se de que Xmax também deve ser <= sqrt (A).

Depois, tente todos os Xs possíveis entre Xmin e Xmax. Não haverá muitos, por isso não vai demorar muito.


Ótima solução! existe um limite para quantos Xs existem?
ciamej

é no máximo sqrt (A) / 2 no caso em que os bits superiores de Y são todos 0. Menos deles serão divisores. Se você está preocupado com isso, você pode reduzir o número de cheque por encontrar os divisores com o método de fatoração de Fermat: en.wikipedia.org/wiki/Fermat%27s_factorization_method
Matt Timmermans

11
Este é um bom insight (+1), mas se estamos falando de números inteiros de 64 bits, o sqrt (A) / 2 pode ser superior a um bilhão. Parece que isso ainda seria lento demais para uma situação típica de "programação competitiva". (Isenção de responsabilidade: eu nunca fiz uma competição de programação, talvez eu esteja errado sobre isso.) Talvez exista uma visão adicional que possa ser combinada com essa de alguma forma?
Ruakh

2
Se você usar o método de Fermat para encontrar os possíveis divisores no intervalo, eu acho que se reduz a sqrt (sqrt (A)), que é certamente OK
Matt Timmermans

6

A outra maneira direta de resolver esse problema é o fato de que os n bits inferiores de XY e X xor Y dependem apenas dos n bits inferiores de X e Y. Portanto, você pode usar as respostas possíveis para restringir os n bits inferiores as respostas possíveis para os n + 1 bits mais baixos , até você terminar.

Descobri que, infelizmente, pode haver mais de uma possibilidade para um único n . Não sei com que frequência haverá muitas possibilidades, mas provavelmente não é com muita frequência, se é que isso pode ser bom em um contexto competitivo. Probabilisticamente, haverá apenas algumas possibilidades, uma vez que uma solução para n bits fornecerá 0 ou duas soluções para n + 1 bits, com igual probabilidade.

Parece funcionar muito bem para entrada aleatória. Aqui está o código que eu usei para testá-lo:

public static void solve(long A, long B)
{
    List<Long> sols = new ArrayList<>();
    List<Long> prevSols = new ArrayList<>();
    sols.add(0L);
    long tests=0;
    System.out.print("Solving "+A+","+B+"... ");
    for (long bit=1; (A/bit)>=bit; bit<<=1)
    {
        tests += sols.size();
        {
            List<Long> t = prevSols;
            prevSols = sols;
            sols = t;
        }
        final long mask = bit|(bit-1);
        sols.clear();
        for (long prevx : prevSols)
        {
            long prevy = (prevx^B) & mask;
            if ((((prevx*prevy)^A)&mask) == 0)
            {
                sols.add(prevx);
            }
            long x = prevx | bit;
            long y = (x^B)&mask;
            if ((((x*y)^A)&mask) == 0)
            {
                sols.add(x);
            }
        }
    }
    tests += sols.size();
    {
        List<Long> t = prevSols;
        prevSols = sols;
        sols = t;
    }
    sols.clear();
    for (long testx: prevSols)
    {
        if (A/testx >= testx)
        {
            long testy = B^testx;
            if (testx * testy == A)
            {
                sols.add(testx);
            }
        }
    }

    System.out.println("" + tests + " checks -> X=" + sols);
}
public static void main(String[] args)
{
    Random rand = new Random();
    for (int range=Integer.MAX_VALUE; range > 32; range -= (range>>5))
    {
        long A = rand.nextLong() & Long.MAX_VALUE;
        long X = (rand.nextInt(range)) + 2L;
        X|=1;
        long Y = A/X;
        if (Y==0)
        {
            Y = rand.nextInt(65536);
        }
        Y|=1;
        solve(X*Y, X^Y);
    }
}

Você pode ver os resultados aqui: https://ideone.com/cEuHkQ

Parece que normalmente leva apenas alguns milhares de cheques.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.