สำหรับจำนวนเต็มสองจำนวน A และ B ให้หาคู่ของตัวเลข X และ Y ที่ A = X * Y และ B = X xor Y


22

ฉันกำลังดิ้นรนกับปัญหานี้ฉันพบในหนังสือการเขียนโปรแกรมการแข่งขัน แต่ไม่มีวิธีแก้ปัญหาที่จะทำ

สำหรับสองจำนวนเต็มAและB (สามารถใส่ในประเภทจำนวนเต็ม 64- บิต) โดยที่Aเป็นเลขคี่หาคู่ของตัวเลข X และ Y เพื่อให้A = X * Y และB = X x หรือ Y วิธีการของฉันคือรายการ ตัวหารทั้งหมดของ A และลองจับคู่ตัวเลขภายใต้ sqrt (A) กับตัวเลขเหนือ sqrt (A) ที่คูณถึงA และดูว่า xor ของพวกเขาเท่ากับBหรือไม่ แต่ฉันไม่รู้ว่ามันมีประสิทธิภาพเพียงพอหรือไม่ อะไรจะเป็นทางออกที่ดี / อัลกอริทึมสำหรับปัญหานี้


1
มันแปลกที่จะผสมตัวดำเนินการจำนวนเต็มกับตัวดำเนินการระดับบิต มันเป็นจริงX*YหรือX&Y?
Eric Duminil

มันคือการคูณ (*)
Aster W.

คุณได้เขียนบรรทัดของรหัสใด ๆ เพื่อแก้ไขงานนี้หรือไม่? คุณต้องการใช้ภาษาโปรแกรมภาษาใด
คม 242

คำตอบ:


5

นี่เป็นการเรียกซ้ำง่าย ๆ ที่สังเกตกฎที่เรารู้: (1) บิตที่สำคัญน้อยที่สุดของทั้ง X และ Y ถูกตั้งค่าเนื่องจากมีหลายคี่เท่านั้นที่ให้ผลคูณที่แปลก (2) ถ้าเราตั้งค่า X เพื่อให้บิตที่ตั้งค่าสูงสุดของ B, Y ไม่สามารถมากกว่า sqrt (A); และ (3) ตั้งค่าบิตใน X หรือ Y ตามบิตปัจจุบันเป็น B

รหัสงูใหญ่ต่อไปในภายใต้ผล 300 ซ้ำสำหรับทุกคน แต่หนึ่งในคู่สุ่มผมหยิบมาจากแมตต์ Timmermans' โค้ดตัวอย่าง แต่คนแรกเอาซ้ำ 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
Matt Timmermans

@ MattTimmermans จับที่ดี ฉันได้เพิ่มการจัดการและตัวอย่างของคุณในการทดสอบซึ่งแก้ไขได้ใน 59 การทำซ้ำ โปรดแจ้งให้เราทราบหากคุณพบปัญหาอื่น ๆ (หรือหากปัญหานี้ดูเหมือนไม่ได้รับการแก้ไข)
גלעדברקן

น่าสนใจ ฉันคาดหวังว่าจะมีราคาแพงเมื่อคุณได้มันทำงาน เรารู้ว่ามีคดีที่มีราคาแพงจากคดี 231199 แต่การพิสูจน์ว่าเป็นเรื่องยาก อย่างไรก็ตามดูเหมือนว่ามันจะทำงานได้ดีในขณะนี้
Matt Timmermans

9

คุณรู้ว่าอย่างน้อยหนึ่งปัจจัยคือ <= sqrt (A) มาสร้าง X กันเถอะ

ความยาวของ X เป็นบิตจะเป็นครึ่งหนึ่งของความยาว A

บิตบนของ X ดังนั้น - ค่าที่สูงกว่า sqrt (A) - คือ 0 ทั้งหมดและบิตที่สอดคล้องกันใน B ต้องมีค่าเดียวกันกับบิตที่สอดคล้องกันใน Y

การรู้บิตส่วนบนของ Y จะให้ช่วงที่ค่อนข้างเล็กสำหรับปัจจัยที่เกี่ยวข้อง X = A / Y คำนวณ Xmin และ Xmax ที่สอดคล้องกับค่าที่มากที่สุดและน้อยที่สุดสำหรับ Y ตามลำดับ โปรดจำไว้ว่า Xmax จะต้องเป็น <= sqrt (A) ด้วย

จากนั้นลองใช้ Xs ที่เป็นไปได้ทั้งหมดระหว่าง Xmin และ Xmax จะมีไม่มากเกินไปดังนั้นจึงใช้เวลาไม่นาน


ทางออกที่ดี! มีข้อ จำกัด เกี่ยวกับจำนวน Xs ที่มีอยู่ไหม?
ciamej

มันมากที่สุด sqrt (A) / 2 ในกรณีที่บิตบนของ Y ทั้งหมด 0 มีน้อยกว่าพวกมันจะเป็นตัวหาร หากคุณกังวลเกี่ยวกับเรื่องนี้คุณสามารถลดจำนวนการตรวจสอบโดยการค้นหาตัวหารด้วยวิธีการแยกตัวประกอบของแฟร์มาต์: en.wikipedia.org/wiki/Fermat%27s_factorization_method
แมตต์ทิมเมอร์แมนส

1
นี่เป็นข้อมูลเชิงลึกที่ดี (+1) แต่ถ้าเราพูดถึงจำนวนเต็ม 64 บิต sqrt (A) / 2 อาจมากกว่าพันล้าน ดูเหมือนว่าจะยังช้าเกินไปสำหรับสถานการณ์ "โปรแกรมการแข่งขัน" ทั่วไป (ข้อจำกัดความรับผิดชอบ: ฉันไม่เคยแข่งขันการเขียนโปรแกรมมาก่อนบางทีฉันอาจผิดเกี่ยวกับเรื่องนี้) อาจมีข้อมูลเชิงลึกเพิ่มเติมที่สามารถนำมารวมกับสิ่งนี้ได้บ้าง
ruakh

2
หากคุณใช้วิธีการของแฟร์มาต์ในการหาตัวหารที่เป็นไปได้ในช่วงนั้นฉันคิดว่ามันจะลดลงเป็น sqrt (sqrt (A)) ซึ่งแน่นอนว่าใช้ได้
Matt Timmermans

6

อีกวิธีที่ตรงไปตรงมาในการแก้ปัญหานี้ขึ้นอยู่กับความจริงที่ว่าบิตnส่วนล่างของ XY และ X xor Y ขึ้นอยู่กับเฉพาะบิตnบิตต่ำกว่าของ X และ Y ดังนั้นคุณสามารถใช้คำตอบที่เป็นไปได้สำหรับบิตnล่างเพื่อ จำกัด คำตอบที่เป็นไปได้สำหรับบิตn + 1 ที่ต่ำกว่าจนกว่าคุณจะทำเสร็จ

ผมเคยทำงานให้เห็นว่าโชคไม่ดีที่สามารถมีได้มากกว่าหนึ่งความเป็นไปได้สำหรับเดียวn ผมไม่ทราบว่าวิธีการที่มักจะมีจำนวนมากเป็นไปได้ แต่ก็อาจจะไม่บ่อยเกินไปถ้าทุกดังนั้นนี้อาจจะปรับในบริบทของการแข่งขัน ความน่าจะเป็นมีความเป็นไปได้เพียงไม่กี่อย่างเท่านั้นเนื่องจากโซลูชันสำหรับnบิตจะให้โซลูชันทั้ง 0 หรือสองสำหรับn + 1บิตโดยมีความน่าจะเป็นเท่ากัน

ดูเหมือนว่าจะทำงานได้ดีสำหรับการป้อนข้อมูลแบบสุ่ม นี่คือรหัสที่ฉันใช้ในการทดสอบ:

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

ดูเหมือนว่าโดยปกติจะใช้เวลาเพียงสองสามพันเช็ค

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.