Per due interi interi A e B, trova una coppia di numeri X e Y tali che A = X * Y e B = X xo Y


22

Sto lottando con questo problema che ho trovato in un libro di programmazione competitivo, ma senza una soluzione su come farlo.

Per due interi interi A e B (può andare bene nel tipo intero a 64 bit), dove A è dispari, trova una coppia di numeri X e Y in modo tale che A = X * Y e B = X xo Y. Il mio approccio era di elencare tutti i divisori di a e provare l'abbinamento numeri sotto sqrt (a) con numeri su sqrt (a) che si moltiplicano fino a a e vedere se la loro xor è uguale a B . Ma non so se sia abbastanza efficiente. Quale sarebbe una buona soluzione / algoritmo a questo problema?


1
È strano mescolare un operatore intero e un operatore bit a bit. È davvero X*Yo X&Y?
Eric Duminil,

È moltiplicazione. (*)
Aster W.

Hai già scritto qualche riga di codice per risolvere questo compito? Quale linguaggio di programmazione intendi utilizzare?
Lynx, 242

Risposte:


5

Ecco una semplice ricorsione che osserva le regole che conosciamo: (1) i bit meno significativi sia di X che di Y sono impostati poiché solo i moltiplicatori dispari producono un multiplo dispari; (2) se impostiamo X per avere il bit di B più alto impostato, Y non può essere maggiore di sqrt (A); e (3) impostare i bit in X o Y in base al bit corrente in B.

Il seguente codice Python ha prodotto meno di 300 iterazioni per tutte tranne una delle coppie casuali che ho scelto dal codice di esempio di Matt Timmermans . Ma il primo ha preso 231.199 iterazioni :)

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 ""

Produzione:

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

Questo non funziona quando B <sqrt (A), ad esempio, quando X == Y
Matt Timmermans,

X == Y è solo l'esempio più semplice. B può essere qualsiasi numero <sqrt (A), come X = 0x30000001, Y = 0x30000007, A = X * Y, B = 6
Matt Timmermans

@MattTimmermans ottima cattura. Ho aggiunto la gestione e il tuo esempio ai test, che si risolvono in 59 iterazioni. Per favore fatemi sapere se trovate altri problemi (o se questo problema sembra non risolto).
עדלעד ברקן

Interessante. Mi aspettavo che quello fosse costoso quando l'hai fatto funzionare. Sappiamo che ci sono casi costosi da quello del 231199, ma è difficile caratterizzarli. Comunque sembra che ora funzioni bene.
Matt Timmermans,

9

Sai che almeno un fattore è <= sqrt (A). Facciamo quella X.

La lunghezza di X in bit sarà circa la metà della lunghezza di A.

I bit superiori di X, quindi - quelli con un valore superiore a sqrt (A) - sono tutti 0 e i bit corrispondenti in B devono avere lo stesso valore dei bit corrispondenti in Y.

Conoscere i bit superiori di Y ti dà un intervallo piuttosto piccolo per il corrispondente fattore X = A / Y. Calcola Xmin e Xmax corrispondenti ai valori più grandi e più piccoli possibili per Y, rispettivamente. Ricorda che Xmax deve anche essere <= sqrt (A).

Quindi prova tutte le possibili X tra Xmin e Xmax. Non ce ne saranno troppi, quindi non ci vorrà molto tempo.


Bella soluzione! c'è un limite a quante X esistono?
ciamej,

è al massimo sqrt (A) / 2 nel caso in cui i bit superiori di Y sono tutti 0. Tuttavia, meno saranno divisori. Se sei preoccupato, puoi ridurre il numero da verificare trovando i divisori con il metodo di fattorizzazione di Fermat: en.wikipedia.org/wiki/Fermat%27s_factorization_method
Matt Timmermans,

1
Questa è una buona idea (+1), ma se stiamo parlando di numeri interi a 64 bit, allora sqrt (A) / 2 può essere più di un miliardo. Sembra che sarebbe ancora troppo lento per una tipica situazione di "programmazione competitiva". (Dichiarazione di non responsabilità: non ho mai partecipato a una competizione di programmazione, forse mi sbaglio su questo.) Forse c'è un'ulteriore intuizione che può essere combinata con questa in qualche modo?
Ruakh,

2
Se usi il metodo fermat per trovare i possibili divisori nell'intervallo, penso che si riduca a sqrt (sqrt (A)), il che è certamente OK
Matt Timmermans,

6

L'altro modo semplice per risolvere questo problema si basa sul fatto che i n bit inferiori di XY e X xor Y dipendono solo dai bit inferiori n di X e Y. Pertanto, è possibile utilizzare le risposte possibili per i bit inferiori n per limitare le possibili risposte per i n + 1 bit inferiori , fino al termine.

Ho capito che, sfortunatamente, ci può essere più di una possibilità per un singolo n . Non so quante volte ci saranno molte possibilità, ma probabilmente non è troppo spesso, quindi potrebbe andare bene in un contesto competitivo. Probabilmente, ci saranno solo poche possibilità, poiché una soluzione per n bit fornirà 0 o due soluzioni per n + 1 bit, con uguale probabilità.

Sembra funzionare abbastanza bene per input casuali. Ecco il codice che ho usato per testarlo:

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);
    }
}

Puoi vedere i risultati qui: https://ideone.com/cEuHkQ

Sembra che di solito ci vogliono solo un paio di migliaia di controlli.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.