Pour deux entiers A et B donnés, trouvez une paire de nombres X et Y tels que A = X * Y et B = X xor Y

22

Je me bats avec ce problème que j'ai trouvé dans un livre de programmation compétitif, mais sans solution comment le faire.

Pour deux entiers donnés A et B (pouvant tenir dans un type entier 64 bits), où A est impair, trouvez une paire de nombres X et Y tels que A = X * Y et B = X xor Y. Mon approche était de lister tous les diviseurs de A et essayer d' appariement des nombres sous sqrt (A) avec des nombres plus sqrt (A) qui se multiplient à A et voir si leur xor est égal à B . Mais je ne sais pas si c'est assez efficace. Quelle serait une bonne solution / algorithme à ce problème?

Aster W.
la source
1
C'est bizarre de mélanger un opérateur entier et un opérateur au niveau du bit. Est-ce vraiment X*You X&Y?
Eric Duminil
C'est la multiplication. (*)
Aster W.
Avez-vous déjà écrit une ligne de code pour résoudre cette tâche? Quel langage de programmation envisagez-vous d'utiliser?
Lynx 242

Réponses:

5

Voici une récursivité simple qui observe les règles que nous connaissons: (1) les bits les moins significatifs de X et Y sont définis car seuls les multiplicandes impairs donnent un multiple impair; (2) si nous réglons X pour avoir le bit de B le plus élevé, Y ne peut pas être supérieur à sqrt (A); et (3) définir des bits dans X ou Y en fonction du bit actuel dans B.

Le code Python suivant a entraîné moins de 300 itérations pour toutes les paires aléatoires, sauf une, que j'ai choisies dans l' exemple de code de Matt Timmermans . Mais le premier a pris 231 199 itérations :)

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

Production:

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
גלעד ברקן
la source
Cela ne fonctionne pas lorsque B <sqrt (A), par exemple, lorsque X == Y
Matt Timmermans
X == Y est juste l'exemple le plus simple. B peut être n'importe quel nombre <sqrt (A), comme X = 0x30000001, Y = 0x30000007, A = X * Y, B = 6
Matt Timmermans
@MattTimmermans grande capture. J'ai ajouté la manipulation et votre exemple aux tests, qui se résolvent en 59 itérations. Veuillez me faire savoir si vous trouvez d'autres problèmes (ou si ce problème semble non résolu).
גלעד ברקן
Intéressant. Je m'attendais à ce que celui-ci soit cher lorsque vous l'avez fait fonctionner. Nous savons qu'il y a des cas coûteux à partir du 231199, mais il s'avère difficile de les caractériser. Quoi qu'il en soit, il semble que cela fonctionne bien maintenant.
Matt Timmermans
9

Vous savez qu'au moins un facteur est <= sqrt (A). Faisons celui-là X.

La longueur de X en bits sera environ la moitié de la longueur de A.

Les bits supérieurs de X, donc - ceux dont la valeur est supérieure à sqrt (A) - sont tous à 0, et les bits correspondants dans B doivent avoir la même valeur que les bits correspondants dans Y.

Connaître les bits supérieurs de Y vous donne une assez petite plage pour le facteur correspondant X = A / Y. Calculez Xmin et Xmax correspondant aux valeurs les plus grandes et les plus petites possibles pour Y, respectivement. N'oubliez pas que Xmax doit également être <= sqrt (A).

Essayez ensuite tous les X possibles entre Xmin et Xmax. Il n'y en aura pas trop, donc ça ne prendra pas très longtemps.

Matt Timmermans
la source
Bonne solution! existe-t-il une limite sur le nombre de ces X?
ciamej
c'est au plus sqrt (A) / 2 dans le cas où les bits supérieurs de Y sont tous à 0. Cependant, moins ils seront diviseurs. Si cela vous inquiète, vous pouvez réduire le nombre à vérifier en trouvant les diviseurs avec la méthode de factorisation de Fermat: en.wikipedia.org/wiki/Fermat%27s_factorization_method
Matt Timmermans
1
C'est un bon aperçu (+1), mais si nous parlons d'entiers 64 bits, alors sqrt (A) / 2 peut être plus d'un milliard. Il semble que cela serait encore trop lent pour une situation typique de "programmation compétitive". (Avertissement: je n'ai jamais participé à un concours de programmation, peut-être que je me trompe à ce sujet.)
ruakh
2
Si vous utilisez la méthode de fermat pour trouver les diviseurs possibles dans la plage, je pense que cela se réduit à sqrt (sqrt (A)), ce qui est certainement OK
Matt Timmermans
6

L'autre façon simple de résoudre ce problème repose sur le fait que les n bits inférieurs de XY et X xor Y dépendent uniquement des n bits inférieurs de X et Y. Par conséquent, vous pouvez utiliser les réponses possibles pour les n bits inférieurs pour restreindre les réponses possibles pour les n + 1 bits inférieurs , jusqu'à ce que vous ayez terminé.

J'ai compris que, malheureusement, il peut y avoir plus d'une possibilité pour un seul n . Je ne sais pas combien de fois il y aura beaucoup de possibilités, mais ce n'est probablement pas trop souvent, donc cela peut être bien dans un contexte concurrentiel. Probablement, il n'y aura que quelques possibilités, car une solution pour n bits fournira soit 0, soit deux solutions pour n + 1 bits, avec une probabilité égale.

Il semble fonctionner assez bien pour une entrée aléatoire. Voici le code que j'ai utilisé pour le tester:

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

Vous pouvez voir les résultats ici: https://ideone.com/cEuHkQ

On dirait que cela ne prend généralement que quelques milliers de chèques.

Matt Timmermans
la source