对于给定的两个整数A和B,找到一对数字X和Y,使得A = X * Y和B = X x或Y


22

我正在为一本竞争激烈的编程书中的这个问题而苦苦挣扎,但没有解决方案。

对于给定的两个整数AB(可以适合64位整数类型),其中A是奇数,请找到一对数字X和Y,使得A = X * Y和B = X xorY。我的方法是列出A的所有约数,并尝试过的sqrt(A)下的sqrt(A)与数字配对数字,乘达 ,看看他们的XOR等于。但是我不知道这是否足够有效。什么是解决这个问题的好方法/算法?


1
混合使用整数运算符和按位运算符是很奇怪的。难道真的X*Y还是X&Y
埃里克·杜米尼尔

它是乘法。(*)
Aster W.19年

您是否已经编写任何代码行来解决此任务?您打算使用哪种编程语言?
山猫242

Answers:


5

这是一个遵循我们已知规则的简单递归:(1)X和Y的最低有效位都已设置,因为只有奇数被乘数会产生奇数倍;(2)如果将X设置为B的最高设置位,则Y不能大于sqrt(A);(3)根据B中的当前位设置X或Y中的位。

以下Python代码针对我从Matt Timmermans的示例代码中挑选的随机对中的一个对进行了不到300次迭代。但是第一个进行了231,199次迭代:)

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

输出:

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

当B <sqrt(A)时,例如当X == Y时,这将不起作用
Matt Timmermans

X == Y只是最简单的例子。B可以是<sqrt(A)的任意数字,例如X = 0x30000001,Y = 0x30000007,A = X * Y,B = 6
马特·蒂默曼斯

@MattTimmermans很棒。我已经将处理和您的示例添加到了测试中,可以在59次迭代中解决。如果您发现其他问题(或者该问题似乎尚未解决),请告诉我。
גלעדברקן

有趣。我希望当它开始工作时,它会很昂贵。我们知道231199机箱中有很多昂贵的机箱,但事实证明,很难对它们进行表征。无论如何,看起来现在工作正常。
马特·蒂默曼斯

9

您知道至少一个因素是<= sqrt(A)。让我们做一个X。

X的长度(以位为单位)将约为A的一半。

因此,X的高位(即值比sqrt(A)高的位)全为0,并且B中的相应位必须与Y中的相应位具有相同的值。

知道Y的高位,就可以得出对应因数X = A / Y的很小范围。计算Xmin和Xmax分别对应于Y的最大和最小可能值。请记住,Xmax也必须小于等于sqrt(A)。

然后,只需尝试Xmin和Xmax之间的所有可能的X。不会太多,因此不会花费很长时间。


不错的解决方案!存在多少个这样的X有界限吗?
ciamej

在Y的高位全为0的情况下,最多为sqrt(A)/ 2。如果您担心它,可以通过使用Fermat的因式分解方法找到除数来减少检查的次数:en.wikipedia.org/wiki/Fermat%27s_factorization_method
Matt Timmermans

1
这是一个很好的见解(+1),但是如果我们谈论的是64位整数,则sqrt(A)/ 2可能超过10亿。对于典型的“竞争性编程”情况来说,这似乎仍然太慢。(免责声明:我从未参加过编程竞赛,也许对此我错了。)也许还有进一步的见解可以与这种观点相结合?
ruakh

2
如果你使用费马的方法查找范围可能除数,我认为它减少到开方(SQRT(A)),这当然是OK
马特TIMMERMANS

6

解决这个问题的另一个直接的方式依赖于一个事实,即较低的ñ XY和X XOR的Y位仅仅依靠低ñ X和Y的位。因此,你可以使用可能的答案为低ň位限制低n + 1位的可能答案,直到完成。

我已经得出结论,不幸的是,单个n可能有不止一种可能性。我不知道会有多少种可能性,但是如果可能的话,这种可能性可能不太常见,因此在竞争环境中这可能很好。概率上只有少数可能性,因为n位的解决方案将为n + 1提供0或两个解决方案相等的概率位。

对于随机输入,它似乎工作得很好。这是我用来测试的代码:

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

您可以在这里查看结果:https : //ideone.com/cEuHkQ

看起来通常只需要几千张支票。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.