มีตัวต่อซูโดกุกี่ตัว?


10

นี่ไม่ใช่ตัวแก้ Sudoku หรือตัวตรวจสอบ Sudoku

ความท้าทายของคุณคือการเขียนฟังก์ชั่นหรือสคริปต์ที่ป้อนขนาด "บล็อก" ของปริศนา Sudoku 2D (ซึ่งเป็น 3 สำหรับบอร์ด 9x9 แบบคลาสสิก 4 สำหรับบอร์ด 16x16เป็นต้น) จะคำนวณจำนวนโดยประมาณ ของปริศนาที่แตกต่างกัน (โซลูชัน) ที่มีอยู่สำหรับขนาดนั้น

ตัวอย่างเช่นการป้อนข้อมูลที่กำหนด 3 โปรแกรมของคุณควรพิมพ์การประมาณเพื่อความแม่นยำที่ต้องการของจำนวน 6,670,903,752,021,072,972,936,960 ซึ่งเป็นจำนวนที่รู้จักกันของปริศนาซูโดกุ 9x9 ที่แตกต่างกันหรือ 5,472,730,538 เมื่อคำนึงถึงความสมมาตรต่างๆ วิธีการแก้ปัญหาของคุณควรระบุว่าจะนับหรือละเว้นสมมาตร

"ความแม่นยำที่ต้องการ" ถูกทิ้งไว้โดยไม่ได้กำหนด: โปรแกรมของคุณอาจทำงานตามเวลาที่กำหนดจากนั้นให้ผลลัพธ์ออกมาหรือคำนวณให้ได้ตามจำนวนตัวเลขที่มีนัยสำคัญหรือแม้แต่เรียกใช้ตลอดไปพิมพ์ที่ดีขึ้นและดีขึ้นโดยประมาณ ประเด็นก็คือมันควรจะเป็นไปได้ที่จะทำให้มันคำนวณผลลัพธ์เพื่อความแม่นยำที่จำเป็นใด ๆ ในเวลาที่ จำกัด (ดังนั้น "42" จึงไม่ใช่คำตอบที่ยอมรับได้) การ จำกัด ความแม่นยำของผลลัพธ์ของคุณกับการลอยของเครื่องที่มีอยู่จึงเป็นที่ยอมรับ

ไม่มีการเข้าถึงทรัพยากรออนไลน์ไม่มีการจัดเก็บซอร์สโค้ดในชื่อไฟล์ ฯลฯ


PS: ฉันรู้ว่านี่เป็นปัญหาที่ยาก (NP- สมบูรณ์ถ้าฉันไม่ผิดพลาด) แต่คำถามนี้จะขอเพียงวิธีการแก้ปัญหาทางสถิติโดยประมาณ ตัวอย่างเช่นคุณสามารถลองการกำหนดค่าแบบสุ่มที่เป็นไปตามข้อ จำกัด หนึ่งข้อ (หรือสองข้อที่ดีกว่า) คำนวณจำนวนที่มีอยู่จากนั้นตรวจสอบความถี่ที่คุณได้รับตัวต่อที่สอดคล้องกับข้อ จำกัด ทั้งสามข้อ สิ่งนี้จะทำงานในเวลาที่เหมาะสมสำหรับขนาดเล็ก (แน่นอนสำหรับ size = 3 และอาจเป็น 4) แต่อัลกอริทึมควรเป็นแบบทั่วไปพอที่จะทำงานกับทุกขนาด

อัลกอริทึมที่ดีที่สุดชนะ


PS2: ฉันเปลี่ยนจาก code-golf เป็น code-challenge เพื่อสะท้อนความยากของปัญหาและกระตุ้นให้เกิดการแก้ปัญหาที่ชาญฉลาดมากกว่าคนที่โง่ แต่เก่ง แต่เนื่องจาก "อัลกอริทึมที่ดีที่สุด" เห็นได้ชัดว่าไม่มีความชัดเจนให้ฉันพยายามกำหนดอย่างถูกต้อง

ให้เวลามากพอและไม่คำนึงถึงปัจจัยคงที่ (รวมถึง CPU และความเร็ว intepreter) หรือเท่ากันเมื่อพิจารณาพฤติกรรมที่ไม่เชิงเส้นของมันการแก้ปัญหาแบบไหนที่จะมาบรรจบกันกับผลลัพธ์ที่แน่นอนที่สุด?


11
นี่ไม่ใช่ปัญหาที่ยากจริงๆ ใช่ไหม คุณแค่ขอวิธีที่สั้นที่สุดในการสร้างฟังก์ชั่นเพื่อสร้างตัวเลข {1, 1, 288, 6e21} หรือเพื่อขยายให้เป็น n> 3
algorithmshark

วิธีการแก้ปัญหาที่แน่นอนนั้นเป็นปัญหาที่ยากอย่างเหลือเชื่อ แต่การประมาณนั้นสามารถคำนวณได้ด้วยการสุ่มตัวอย่างแบบสุ่มและเวลา CPU ที่ทันสมัยเพียงไม่กี่วินาที แน่นอนว่าโซลูชั่นที่ชาญฉลาดยินดีต้อนรับ!
เบีย

2
@Tobia วิธีการนี้ใช้เพื่อหาจำนวนตำแหน่งคิวบิกของรูบิคโดยประมาณซึ่งต้องมีการย้าย N เพื่อแก้kociemba.org/cube.htmดังนั้นจึงเป็นไปได้ที่จะได้รับการประมาณด้วยวิธีนี้ อย่างไรก็ตามถ้าฉันเขียนโปรแกรมที่ทำให้ทุกแถวได้รับการแก้ไขแล้วทดสอบเพื่อดูว่าคอลัมน์ & สี่เหลี่ยมจัตุรัสได้รับการแก้ไขหรือไม่มันจะมี (9!) ^ 9 = 1E50 ความเป็นไปได้ที่จะ bruteforce ซึ่งมีเพียง 6E21 ต่อการตอบคำถาม .) จะต้องพยายาม 1.6E28 ต่อการโจมตีหนึ่งครั้งโดยเฉลี่ย ค่อนข้างช้า ทีนี้ถ้าฉันมั่นใจได้ว่าทั้งแถวและคอลัมน์นั้นถูกต้องและตรวจสอบกำลังสองเท่านั้นฉันจะไปที่อื่น อา! ฉันมีความคิด ...
Level River St

@steveverrill เห็นไหม? :-)
Tobia

ไม่มีโซลูชันการวิเคราะห์ใช่ไหม
Newbrict

คำตอบ:


3

C ++

สิ่งที่ฉันจะนำเสนอในที่นี้คืออัลกอริทึมที่แสดงด้วยตัวอย่างสำหรับกรณี 3x3 ในทางทฤษฎีมันสามารถขยายไปถึงเคส NxN ได้ แต่มันต้องมีคอมพิวเตอร์ที่ทรงพลังกว่าและ / หรือมีการปรับแต่งอันชาญฉลาด ฉันจะพูดถึงการปรับปรุงบางอย่างเมื่อฉันผ่าน

ก่อนที่จะไปเพิ่มเติมลองสังเกตความสมดุลของกริด Sudokuนั่นคือการแปลงที่นำไปสู่กริดอื่นในลักษณะที่ไม่สำคัญ สำหรับขนาดบล็อก 3 ความสมมาตรมีดังนี้:

สมมาตรแนวนอน

**The N=3 sudoku is said to consist of 3 "bands" of 3 "rows" each**
permute the three bands: 3! permutations = 6
permute the rows in each band: 3 bands, 3! permutations each =(3!)^3=216

แนวตั้งสมมาตร

**The N=3 sudoku is said to consist of 3 "stacks" of 3 "columns" each.**
the count is the same as for horizontal.

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

2*(N!*(N!)^N)^2 = 2*(6*216)^2=3359232 spatial symmetries for the case N=3.

จากนั้นก็มีความสมมาตรที่สำคัญมากอีกอย่างหนึ่งที่เรียกว่า relabelling

Relabelling gives a further (N^2)!=9!=362880 symmetries for the case N=3. So the total 
number of symmetries is 362880*3359232=1218998108160.

จำนวนทั้งหมดของการแก้ปัญหาไม่สามารถหาได้ง่ายโดยการคูณจำนวนของการแก้ปัญหาที่ไม่ซ้ำกันแบบสมมาตรตามจำนวนนี้เพราะมีจำนวน (น้อยกว่า 1%) ของการแก้ปัญหาอัตโนมัติ นั่นหมายความว่าสำหรับการแก้ปัญหาพิเศษเหล่านี้มีการดำเนินการสมมาตรที่แมปพวกเขากับตัวเองหรือการดำเนินการสมมาตรหลายอย่างที่แมปพวกเขากับโซลูชั่นอื่น ๆ ที่เหมือนกัน

ในการประมาณจำนวนการแก้ปัญหาฉันเข้าใกล้ปัญหาใน 4 ขั้นตอน:

1. เติมอาร์เรย์ที่r[362880][12]มีการเรียงสับเปลี่ยนที่เป็นไปได้ทั้งหมดของตัวเลข 0 ถึง 8 (นี่คือการเขียนโปรแกรมและเป็น C ดังนั้นเราจะไม่ใช้ 1 ถึง 9) หากคุณฉลาดคุณจะสังเกตเห็นว่าตัวห้อยที่สอง คือ 12 ไม่ใช่ 9 นี่เป็นเพราะในขณะที่ทำสิ่งนี้จำไว้ว่าเรากำลังจะพิจารณาสิ่งนี้ให้เป็น "แถว" เรายังคำนวณจำนวนเต็มอีกสามค่าr[9,10,11] == 1<<a | 1<<b | 1<<cโดยที่ 9,10,11 อ้างถึงกองซ้อนที่หนึ่งสองและสาม และ a, b, c เป็นตัวเลขสามตัวที่ปรากฏในแต่ละสแต็กสำหรับแถวนั้น

2. กรอกข้อมูลอาร์เรย์bด้วยวิธีแก้ปัญหาที่เป็นไปได้ทั้งหมดของวง 3 แถว เพื่อให้มีขนาดเล็กพอสมควรให้รวมเฉพาะโซลูชันที่แถวบนสุดคือ 012,345,678 ฉันทำเช่นนี้โดยกำลังดุร้ายโดยการสร้างแถวกลางเป็นไปได้ทั้งหมดและ Anding กับr[0][10,11,12] r[i][10,11,12]ค่าบวกใด ๆ หมายถึงมีตัวเลขที่เหมือนกันสองตัวในจตุรัสเดียวกันและย่านนั้นไม่ถูกต้อง เมื่อมีการรวมกันที่ถูกต้องสำหรับสองแถวแรกฉันค้นหาแถวที่ 3 (ด้านล่าง) ด้วยเทคนิคเดียวกัน

ฉันกำหนดขนาดอาร์เรย์เป็น b [2000000] [9] แต่โปรแกรมค้นหาวิธีแก้ปัญหา 1306368 เท่านั้น ฉันไม่รู้ว่ามีอยู่เท่าไหร่ดังนั้นฉันจึงปล่อยให้มิติอาร์เรย์เป็นแบบนั้น นี่เป็นเพียงวิธีแก้ปัญหาที่เป็นไปได้เพียงครึ่งเดียวสำหรับวงดนตรีเดียว (ตรวจสอบในวิกิพีเดีย) เพราะฉันสแกนเฉพาะแถวที่ 3 จากค่าปัจจุบันiขึ้นไป ส่วนที่เหลือของโซลูชันสามารถพบได้เพียงเล็กน้อยโดยการแลกเปลี่ยนแถวที่ 2 และ 3

วิธีที่ข้อมูลถูกเก็บไว้ในอาร์เรย์bจะทำให้เกิดความสับสนเล็กน้อยในตอนแรก แทนที่จะใช้จำนวนเต็มแต่ละตัวเพื่อเก็บตัวเลขที่0..8พบในตำแหน่งที่กำหนดที่นี่แต่ละจำนวนเต็มพิจารณาหนึ่งในตัวเลข0..8และระบุว่าคอลัมน์ใดที่สามารถพบได้ ดังนั้นb[x][7]==100100001จะระบุว่าสำหรับวิธีการแก้ปัญหา x หมายเลข 7 ถูกพบในคอลัมน์ 0,5 และ 8 (จากขวาไปซ้าย) เหตุผลสำหรับการแทนค่านี้คือเราต้องสร้างส่วนที่เหลือของความเป็นไปได้สำหรับวงดนตรีโดย relabelling และสิ่งนี้ การเป็นตัวแทนทำให้สะดวกในการทำเช่นนี้

สองขั้นตอนข้างต้นประกอบด้วยการตั้งค่าและใช้เวลาประมาณหนึ่งนาที (อาจน้อยกว่านี้หากฉันลบการส่งออกข้อมูลที่ไม่จำเป็นออกไปสองขั้นตอนด้านล่างเป็นการค้นหาจริง)

3 ค้นหาแบบสุ่มเพื่อหาคำตอบสำหรับสองวงแรกที่ไม่ได้ปะทะกัน (เช่นไม่มีจำนวนเท่ากันสองครั้งในคอลัมน์ที่กำหนดเราเลือกวิธีสุ่มสำหรับวง 1 โดยสมมติว่ามีการเปลี่ยนแปลงเสมอ 0 และสุ่มวิธีสำหรับวง 2 ด้วย การเปลี่ยนรูปแบบสุ่มผลลัพธ์มักจะพบในการพยายามน้อยกว่า 9999 ครั้ง (อัตราการโจมตีระยะที่หนึ่งในช่วงพัน) และใช้เวลาเพียงเสี้ยววินาทีโดยการเรียงสับเปลี่ยนฉันหมายถึงว่าสำหรับวงที่สองเราใช้วิธีแก้ปัญหาจาก b [] [] โดยที่แถวแรกจะเป็น 012,345,678 เสมอและติดตั้งใหม่เพื่อให้สามารถเรียงลำดับหมายเลขในแถวแรกได้

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

เมื่อคืนที่ผ่านมาฉันทำมันอย่างที่เป็นไปได้ แต่มันก็ยังน่าสนใจ (เพราะมันไม่มีอะไรเลยสำหรับทุกวัยจากนั้นก็พบทางออกจำนวนมากในการระเบิด) มันใช้เวลาตลอดทั้งคืนเพื่อรับชุดข้อมูลเดียว(!z)ฉันยกเลิกการkวนลูปล่าสุดทันทีที่เรารู้ว่านี่ไม่ใช่วิธีแก้ปัญหาที่ถูกต้อง (ซึ่งทำให้มันทำงานได้เร็วขึ้นเกือบ 9 เท่า) พบ 1186585 โซลูชันสำหรับกริดที่สมบูรณ์ บล็อกทั้งหมดมีความเป็นไปได้ 474054819840 นั่นเป็นอัตราการเข้าชมที่ 1 ใน 400000 สำหรับด่านที่สอง ฉันจะลองอีกครั้งในไม่ช้าด้วยการค้นหาแบบสุ่มมากกว่าการสแกน ควรให้คำตอบที่สมเหตุสมผลเพียงไม่กี่ล้านครั้งซึ่งจะใช้เวลาเพียงไม่กี่วินาที

คำตอบโดยรวมควรเป็น (362880 * (1306368 * 2)) ^ 3 * อัตราการเข้าชม = 8.5E35 * อัตราการเข้าชม โดยการคำนวณย้อนหลังจากตัวเลขในคำถามนั้นฉันคาดว่าอัตราการเข้าชมที่ 1 / 1.2E14 สิ่งที่ฉันได้รับจนถึงตอนนี้กับดาต้าพอยท์เดียวของฉันคือ 1 / (400000 * 1,000) ซึ่งคิดเป็นประมาณหนึ่งล้าน นี่อาจเป็นความผิดปกติของโอกาสข้อผิดพลาดในโปรแกรมของฉันหรือข้อผิดพลาดในคณิตศาสตร์ของฉัน ฉันไม่รู้ว่ามันคืออะไรจนกระทั่งฉันทำการทดสอบเพิ่มเติมอีกสองสามครั้ง

ฉันจะออกจากที่นี่เพื่อคืนนี้ ข้อความเป็นกระท่อนกระแท่นเล็กน้อยฉันจะทำให้เป็นระเบียบขึ้นในไม่ช้าและหวังว่าจะเพิ่มผลลัพธ์เพิ่มเติมและอาจมีคำสองสามคำเกี่ยวกับวิธีทำให้เร็วขึ้นและวิธีขยายแนวคิดให้เป็น N = 4 ฉันไม่คิดว่าฉันจะทำการเปลี่ยนแปลงโปรแกรมของฉันมากเกินไปแม้ว่า :-)

อ่า .. โปรแกรม:

#include "stdafx.h"
#define _CRT_RAND_S
#include <algorithm>  
#include <time.h>

unsigned int n[] = { 0,1,2,3,4,5,6,7,8 }, r[362880][12], b[2000000][9],i,j,k,l,u,v,w,x,y,z;

int main () {

  //Run through all possible permutations of n[] and load them into r[][] 
  i=0;  
  do {
      r[i][9] = r[i][10] = r[i][11]=0;
      for (l = 0; l < 9; l++){
          r[i][l] = n[l];
          r[i][9 + l / 3] |= 1 << n[l];
      }
      if((i+1)%5040==0) printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
          ,r[i][0],r[i][1],r[i][2],r[i][3],r[i][4],r[i][5],r[i][6],r[i][7],r[i][8],r[i][9],r[i][10],r[i][11],r[i][9]+r[i][10]+r[i][11]);
      i++;
  } while ( std::next_permutation(n,n+9) );

  //Initialise b[][]
  for (l = 0; l<2000000; l++) for (k = 0; k<9; k++) b[l][k]=0;
  //fill b[][] with all solutions of the first band, where row0 ={0,1,2,3,4,5,6,7,8} and row1<row2 
  l=0;
  for (i = 0; i<362880; i++) 
  if (!(r[0][9] & r[i][9] | r[0][10] & r[i][10] | r[0][11] & r[i][11])){printf("%d %d \n",i,l);
     for (j=i; j<362880;j++) 
       if(!(r[0][9]&r[j][9] | r[0][10]&r[j][10] | r[0][11]&r[j][11] | r[j][9]&r[i][9] | r[j][10]&r[i][10] | r[j][11]&r[i][11] )){
           for (k = 0; k < 9; k++){
               b[l][r[0][k]]|=1<<k;
               b[l][r[i][k]]|=1<<k;
               b[l][r[j][k]]|=1<<k;
            } 
            l++;
       }
//        printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
//        ,r[i][0],r[i][1],r[i][2],r[i][3],r[i][4],r[i][5],r[i][6],r[i][7],r[i][8],r[i][9],r[i][10],r[i][11],r[i][9]+r[i][10]+r[i][11]);
//        printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
//        ,r[j][0],r[j][1],r[j][2],r[j][3],r[j][4],r[j][5],r[j][6],r[j][7],r[j][8],r[j][9],r[j][10],r[j][11],r[j][9]+r[j][10]+r[j][11]);
//        printf("%d %d %o %o %o %o %o %o %o %o %o \n",i,l,b[l][0],b[l][1],b[l][2],b[l][3],b[l][4],b[l][5],b[l][6],b[l][7],b[l][8]);
  }

  // find a random solution for the first 2 bands
  l=0;
  do{
      rand_s(&u); u /= INT_MIN / -653184; //1st band selection
      rand_s(&v); v /= INT_MIN / -181440; //2nd band permutation
      rand_s(&w); w /= INT_MIN / -653184; //2nd band selection
      z = 0;
      for (k = 0; k < 9; k++) z |= b[u][k] & b[w][r[v][k]];
      l++;
  } while (z);
  printf("finished random after %d tries \n",l);
  printf("found solution with top band %d permutation 0, and middle band %d permutation %d \n",u,w,v);
  getchar();

  // scan all possibilities for the last band
  l=0;
  for (i = 0; i < 362880; i++) for (j = 0; j < 1306368; j++){
              z=0;
              for(k=0;(k<9)&&(!z);k++) z|= b[u][k] & b[j][r[i][k]] | b[j][r[i][k]] & b[w][r[v][k]];
              if (!z){ l++; printf("solution %d : i= %d j=%d",l,i,j); }
  }
  printf("finished bottom band scan at %d millisec \n", clock()); getchar();
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.