สร้าง AI สีน้ำท่วม


34

ในเกม Flood Paint เป้าหมายของเกมคือการทำให้บอร์ดทั้งหมดเป็นสีเดียวกันในเวลาไม่กี่รอบเท่าที่จะทำได้

เกมเริ่มต้นด้วยบอร์ดที่มีลักษณะดังนี้:

3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 5 5 5 4 1 4
6 2 5 3[3]1 1 6 6
5 5 1 2 5 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4

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

3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 5 5 5 4 1 4
6 2 5 5[5]1 1 6 6
5 5 1 2 5 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4

จากนั้น 3 ที่อยู่ทางซ้ายของศูนย์กลาง 3 จะเปลี่ยนสีด้วย ตอนนี้มีทั้งหมดเจ็ด 5 ที่เข้าถึงได้จากจุดศูนย์กลางดังนั้นถ้าเราเปลี่ยนสีเป็น 4:

3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 4 4 4 4 1 4
6 2 4 4[4]1 1 6 6
5 5 1 2 4 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4

ภูมิภาคที่ทาสีจะเพิ่มขนาดขึ้นอีกครั้งอย่างมาก

งานของคุณคือการสร้างโปรแกรมที่จะใช้ตารางสี 19 ถึง 19 จาก 1 ถึง 6 เป็นอินพุตในรูปแบบใดก็ได้ที่คุณเลือก:

4 5 1 1 2 2 1 6 2 6 3 4 2 3 2 3 1 6 3
4 2 6 3 4 4 5 6 4 4 5 3 3 3 3 5 4 3 4
2 3 5 2 2 5 5 1 2 6 2 6 6 2 1 6 6 1 2
4 6 5 5 5 5 4 1 6 6 3 2 6 4 2 6 3 6 6
1 6 4 4 4 4 6 4 2 5 5 3 2 2 4 1 5 2 5
1 6 2 1 5 1 6 4 4 1 5 1 3 4 5 2 3 4 1
3 3 5 3 2 2 2 4 2 1 6 6 6 6 1 4 5 2 5
1 6 1 3 2 4 1 3 3 4 6 5 1 5 5 3 4 3 3
4 4 1 5 5 1 4 6 3 3 4 5 5 6 1 6 2 6 4
1 4 2 5 6 5 5 3 2 5 5 5 3 6 1 4 4 6 6
4 6 6 2 6 6 2 4 2 6 1 5 6 2 3 3 4 3 6
6 1 3 6 3 5 5 3 6 1 3 4 4 5 1 2 6 4 3
2 6 1 3 2 4 2 6 1 1 5 2 6 6 6 6 3 3 3
3 4 5 4 6 6 3 3 4 1 1 6 4 5 1 3 4 1 2
4 2 6 4 1 5 3 6 4 3 4 5 4 2 1 1 4 1 1
4 2 4 1 5 2 2 3 6 6 6 5 2 5 4 5 4 5 1
5 6 2 3 4 6 5 4 1 3 2 3 2 1 3 6 2 2 4
6 5 4 1 3 2 2 1 1 1 6 1 2 6 2 5 6 4 5
5 1 1 4 2 6 2 5 6 1 3 3 4 1 6 1 2 1 2

และกลับลำดับสีที่จัตุรัสกลางจะเปลี่ยนไปในแต่ละเทิร์นอีกครั้งในรูปแบบที่คุณเลือก:

263142421236425431645152623645465646213545631465

ในตอนท้ายของแต่ละลำดับของการเคลื่อนไหวสี่เหลี่ยมในตาราง 19-19 จะต้องมีสีเดียวกัน

โปรแกรมของคุณจะต้องกำหนดอย่างสมบูรณ์; อนุญาตให้ใช้โซลูชันปลอมเทียมได้ แต่โปรแกรมจะต้องสร้างเอาต์พุตเดียวกันสำหรับกรณีทดสอบเดียวกันทุกครั้ง

โปรแกรมที่ชนะจะใช้จำนวนขั้นตอนทั้งหมดน้อยที่สุดในการแก้กรณีทดสอบทั้งหมด 100,000 คดีที่พบในไฟล์นี้ (ไฟล์ข้อความซิปขนาด 14.23 MB) หากสองวิธีดำเนินการตามขั้นตอนจำนวนเท่ากัน (เช่นหากทั้งคู่พบกลยุทธ์ที่เหมาะสมที่สุด) โปรแกรมที่สั้นกว่าจะชนะ


BurntPizza ได้เขียนโปรแกรมใน Java เพื่อตรวจสอบผลการทดสอบ steps.txtการใช้โปรแกรมนี้เรียกใช้การส่งและท่อของคุณออกไปยังไฟล์ที่เรียกว่า จากนั้นเรียกใช้โปรแกรมนี้ด้วยsteps.txtและfloodtestไฟล์ในไดเรกทอรีเดียวกัน หากรายการของคุณถูกต้องและสร้างโซลูชันที่ถูกต้องสำหรับไฟล์ทั้งหมดมันควรผ่านการทดสอบและส่งคืนทั้งหมดAll boards solved successfully.

import java.io.*;
import java.util.*;

public class PainterVerifier {

    public static void main(String[] args) throws FileNotFoundException {

        char[] board = new char[361];

        Scanner s = new Scanner(new File("steps.txt"));
        Scanner b = new Scanner(new File("floodtest"));

        int lineNum = 0;

        caseloop: while (b.hasNextLine()) {

            for (int l = 0; l < 19; l++) {
                String lineb = b.nextLine();
                if (lineb.isEmpty())
                    continue caseloop;
                System.arraycopy(lineb.toCharArray(), 0, board, l * 19, 19);
            }

            String line = s.nextLine();
            if (line.isEmpty())
                continue;
            char[] steps = line.toCharArray();

            Stack<Integer> nodes = new Stack<Integer>();

            for (char c : steps) {
                char targetColor = board[180];
                char replacementColor = c;

                nodes.push(180);

                while (!nodes.empty()) {
                    int n = nodes.pop();
                    if (n < 0 || n > 360)
                        continue;
                    if (board[n] == targetColor) {
                        board[n] = replacementColor;
                        if (n % 19 > 0)
                            nodes.push(n - 1);
                        if (n % 19 < 18)
                            nodes.push(n + 1);
                        if (n / 19 > 0)
                            nodes.push(n - 19);
                        if (n / 19 < 18)
                            nodes.push(n + 19);
                    }
                }
            }
            char center = board[180];
            for (char c : board)
                if (c != center) {
                    s.close();
                    b.close();

                    System.out.println("\nIncomplete board found!\n\tOn line " + lineNum + " of steps.txt");
                    System.exit(0);
                }

            if (lineNum % 5000 == 0)
                System.out.printf("Verification %d%c complete...\n", lineNum * 100 / 100000, '%');

            lineNum++;
        }
        s.close();
        b.close();
        System.out.println("All boards solved successfully.");
    }
}

นอกจากนี้กระดานคะแนนเนื่องจากผลลัพธ์ไม่ได้เรียงตามคะแนนจริงและที่นี่มันสำคัญมาก:

  1. 1,985,078 - smack42, Java
  2. 2,075,452 - ผู้ใช้ 1502040, C
  3. 2,098,382 - tigrou, C #
  4. 2,155,834 - CoderTao, C #
  5. 2,201,995 - MrBackend, Java
  6. 2,383,569 - CoderTao, C #
  7. 2,384,020 - Herjan, C
  8. 2,403,189 - Origineil, Java
  9. 2,445,761 - Herjan, C
  10. 2,475,056 - รายการเจเรมี, แฮสเคลล์
  11. 2,480,714 - SteelTermite, C (2,395 ไบต์)
  12. 2,480,714 - Herjan, Java (4,702 ไบต์)
  13. 2,588,847 - BurntPizza, Java (2,748 ไบต์)
  14. 2,588,847 - Gero3, node.js (4,641 ไบต์)
  15. 2,979,145 - Teun Pronk, Delphi XE3
  16. 4,780,841 - BurntPizza, Java
  17. 10,800,000 - Joe Z. , Python

2
ตัดสินโดยการส่งของคุณเองผลลัพธ์ไม่ควรมีช่องว่างจริงหรือ
Martin Ender

5
เป็นที่น่าสังเกตว่าข้อมูลอินพุตทดสอบไม่มีช่องว่างระหว่างตัวเลข
nderscore

3
คุณยังสามารถเขียนได้ หากมันบั่นทอนผู้ชนะปัจจุบันฉันจะเปลี่ยนคำตอบที่ยอมรับได้
Joe Z.

4
ข้อ จำกัด เวลาคือ "ต้องเร็วพอที่คุณจะรันและโพสต์ผลลัพธ์จริงที่นี่"
Joe Z.

2
@AlexanderRevo ฉันคิดว่าฉันไม่ได้ย้ายไฟล์ แต่เห็นได้ชัดว่าลิงก์นั้นมีการเปลี่ยนแปลงและเปลี่ยนแปลงโดยที่ฉันไม่ทำเช่นนั้น นี่คือลิงค์อีกครั้ง
Joe Z.

คำตอบ:


4

Java - 1,985,078 ขั้นตอน

https://github.com/smack42/ColorFill

อีกรายการล่าช้า แฟ้มผลที่มี 1,985,078 ขั้นตอนสามารถพบได้ที่นี่

ข้อมูลพื้นฐานบางอย่าง:

ฉันค้นพบความท้าทายนี้เมื่อหลายปีก่อนเมื่อฉันเริ่มเขียนโปรแกรมโคลนของเกม Flood-it

"อัลกอริทึมที่ดีที่สุดของที่ไม่สมบูรณ์" DFS และ A *
ตั้งแต่ต้นฉันต้องการสร้างอัลกอริทึมตัวแก้ปัญหาที่ดีสำหรับเกมนี้ เมื่อเวลาผ่านไปฉันสามารถปรับปรุงตัวแก้ปัญหาของฉันโดยการรวมกลยุทธ์หลายอย่างที่ทำให้การค้นหาไม่สมบูรณ์แตกต่างกัน (คล้ายกับที่ใช้ในโปรแกรมอื่นที่นี่) และโดยใช้ผลลัพธ์ที่ดีที่สุดของกลยุทธ์เหล่านั้นสำหรับแต่ละโซลูชัน ฉันใช้อัลกอริทึม A * ของ tigrouอีกครั้งใน Java และเพิ่มลงในตัวแก้ปัญหาของฉันเพื่อให้ได้โซลูชันที่ดีกว่าโดยรวมกว่าผลลัพธ์ของ tigrou

อัลกอริทึม DFS ครบถ้วนสมบูรณ์
แล้วฉันมุ่งเน้นไปที่อัลกอริทึมที่มักจะหาทางออกที่ดีที่สุด ฉันใช้ความพยายามอย่างมากในการปรับกลยุทธ์การค้นหาระดับลึกให้ละเอียด เพื่อเพิ่มความเร็วในการค้นหาฉันรวม hashmap ที่เก็บสถานะการสำรวจทั้งหมดเพื่อให้การค้นหาสามารถหลีกเลี่ยงการสำรวจอีกครั้ง ในขณะที่อัลกอริทึมนี้ทำงานได้ดีและแก้ปริศนาทั้งหมด 14x14 ได้อย่างรวดเร็วเพียงพอ แต่ก็ใช้หน่วยความจำมากเกินไปและทำงานช้ามากด้วยปริศนา 19x19 ในการท้าทายรหัสนี้

Puchert A * ขั้นตอนวิธีการ
ไม่กี่เดือนที่ผ่านมาผมได้รับการติดต่อเพื่อดูที่Flood-It แก้โดยอาโรนและไซมอน Puchert โปรแกรมนั้นใช้อัลกอริทึม A * -type พร้อมฮิวริสติกที่ยอมรับได้ (ตรงกันข้ามกับ tigrou's) และเลื่อนการตัดแต่งกิ่งที่คล้ายกับ Jump-Point Search ฉันสังเกตเห็นอย่างรวดเร็วว่าโปรแกรมนี้เร็วมากและค้นหาวิธีแก้ปัญหาที่ดีที่สุด !

แน่นอนฉันต้องใช้อัลกอริทึมที่ยอดเยี่ยมนี้อีกครั้งและเพิ่มเข้าในโปรแกรมของฉัน ฉันพยายามเพิ่มประสิทธิภาพโปรแกรม Java ของฉันให้ทำงานเร็วเท่ากับโปรแกรม C ++ ดั้งเดิมโดยพี่น้อง Puchert จากนั้นฉันตัดสินใจทำการทดสอบ 100,000 ครั้งของการท้าทายนี้ บนเครื่องของฉันโปรแกรมใช้เวลานานกว่า 120 ชั่วโมงเพื่อค้นหา 1,985,078 ขั้นตอนโดยใช้อัลกอริทึม Puchert A * ของฉัน

นี่จะเป็นทางออกที่ดีที่สุดสำหรับความท้าทายนี้เว้นแต่จะมีข้อบกพร่องบางอย่างในโปรแกรมที่จะส่งผลให้เกิดการแก้ปัญหาย่อยที่ดีที่สุด ข้อเสนอแนะใด ๆ ยินดีต้อนรับ!

แก้ไข: เพิ่มส่วนที่เกี่ยวข้องของรหัสที่นี่:

คลาสAStarPuchertStrategy

/**
 * a specific strategy for the AStar (A*) solver.
 * <p>
 * the idea is taken from the program "floodit" by Aaron and Simon Puchert,
 * which can be found at <a>https://github.com/aaronpuchert/floodit</a>
 */
public class AStarPuchertStrategy implements AStarStrategy {

    private final Board board;
    private final ColorAreaSet visited;
    private ColorAreaSet current, next;
    private final short[] numCaNotFilledInitial;
    private final short[] numCaNotFilled;

    public AStarPuchertStrategy(final Board board) {
        this.board = board;
        this.visited = new ColorAreaSet(board);
        this.current = new ColorAreaSet(board);
        this.next = new ColorAreaSet(board);
        this.numCaNotFilledInitial = new short[board.getNumColors()];
        for (final ColorArea ca : board.getColorAreasArray()) {
            ++this.numCaNotFilledInitial[ca.getColor()];
        }
        this.numCaNotFilled = new short[board.getNumColors()];
    }

    /* (non-Javadoc)
     * @see colorfill.solver.AStarStrategy#setEstimatedCost(colorfill.solver.AStarNode)
     */
    @Override
    public void setEstimatedCost(final AStarNode node) {

        // quote from floodit.cpp: int State::computeValuation()
        // (in branch "performance")
        //
        // We compute an admissible heuristic recursively: If there are no nodes
        // left, return 0. Furthermore, if a color can be eliminated in one move
        // from the current position, that move is an optimal move and we can
        // simply use it. Otherwise, all moves fill a subset of the neighbors of
        // the filled nodes. Thus, filling that layer gets us at least one step
        // closer to the end.

        node.copyFloodedTo(this.visited);
        System.arraycopy(this.numCaNotFilledInitial, 0, this.numCaNotFilled, 0, this.numCaNotFilledInitial.length);
        {
            final ColorAreaSet.FastIteratorColorAreaId iter = this.visited.fastIteratorColorAreaId();
            int nextId;
            while ((nextId = iter.nextOrNegative()) >= 0) {
                --this.numCaNotFilled[this.board.getColor4Id(nextId)];
            }
        }

        // visit the first layer of neighbors, which is never empty, i.e. the puzzle is not solved yet
        node.copyNeighborsTo(this.current);
        this.visited.addAll(this.current);
        int completedColors = 0;
        {
            final ColorAreaSet.FastIteratorColorAreaId iter = this.current.fastIteratorColorAreaId();
            int nextId;
            while ((nextId = iter.nextOrNegative()) >= 0) {
                final byte nextColor = this.board.getColor4Id(nextId);
                if (--this.numCaNotFilled[nextColor] == 0) {
                    completedColors |= 1 << nextColor;
                }
            }
        }
        int distance = 1;

        while(!this.current.isEmpty()) {
            this.next.clear();
            final ColorAreaSet.FastIteratorColorAreaId iter = this.current.fastIteratorColorAreaId();
            int thisCaId;
            if (0 != completedColors) {
                // We can eliminate colors. Do just that.
                // We also combine all these elimination moves.
                distance += Integer.bitCount(completedColors);
                final int prevCompletedColors = completedColors;
                completedColors = 0;
                while ((thisCaId = iter.nextOrNegative()) >= 0) {
                    final ColorArea thisCa = this.board.getColorArea4Id(thisCaId);
                    if ((prevCompletedColors & (1 << thisCa.getColor())) != 0) {
                        // completed color
                        // expandNode()
                        for (final int nextCaId : thisCa.getNeighborsIdArray()) {
                            if (!this.visited.contains(nextCaId)) {
                                this.visited.add(nextCaId);
                                this.next.add(nextCaId);
                                final byte nextColor = this.board.getColor4Id(nextCaId);
                                if (--this.numCaNotFilled[nextColor] == 0) {
                                    completedColors |= 1 << nextColor;
                                }
                            }
                        }
                    } else {
                        // non-completed color
                        // move node to next layer
                        this.next.add(thisCaId);
                    }
                }
            } else {
                // Nothing found, do the color-blind pseudo-move
                // Expand current layer of nodes.
                ++distance;
                while ((thisCaId = iter.nextOrNegative()) >= 0) {
                    final ColorArea thisCa = this.board.getColorArea4Id(thisCaId);
                    // expandNode()
                    for (final int nextCaId : thisCa.getNeighborsIdArray()) {
                        if (!this.visited.contains(nextCaId)) {
                            this.visited.add(nextCaId);
                            this.next.add(nextCaId);
                            final byte nextColor = this.board.getColor4Id(nextCaId);
                            if (--this.numCaNotFilled[nextColor] == 0) {
                                completedColors |= 1 << nextColor;
                            }
                        }
                    }
                }
            }

            // Move the next layer into the current.
            final ColorAreaSet tmp = this.current;
            this.current = this.next;
            this.next = tmp;
        }
        node.setEstimatedCost(node.getSolutionSize() + distance);
    }

}

ส่วนหนึ่งของคลาสAStarSolver

private void executeInternalPuchert(final ColorArea startCa) throws InterruptedException {
    final Queue<AStarNode> open = new PriorityQueue<AStarNode>(AStarNode.strongerComparator());
    open.offer(new AStarNode(this.board, startCa));
    AStarNode recycleNode = null;
    while (open.size() > 0) {
        if (Thread.interrupted()) { throw new InterruptedException(); }
        final AStarNode currentNode = open.poll();
        if (currentNode.isSolved()) {
            this.addSolution(currentNode.getSolution());
            return;
        } else {
            // play all possible colors
            int nextColors = currentNode.getNeighborColors(this.board);
            while (0 != nextColors) {
                final int l1b = nextColors & -nextColors; // Integer.lowestOneBit()
                final int clz = Integer.numberOfLeadingZeros(l1b); // hopefully an intrinsic function using instruction BSR / LZCNT / CLZ
                nextColors ^= l1b; // clear lowest one bit
                final byte color = (byte)(31 - clz);
                final AStarNode nextNode = currentNode.copyAndPlay(color, recycleNode, this.board);
                if (null != nextNode) {
                    recycleNode = null;
                    this.strategy.setEstimatedCost(nextNode);
                    open.offer(nextNode);
                }
            }
        }
        recycleNode = currentNode;
    }
}

ส่วนหนึ่งของคลาสAStarNode

/**
 * check if this color can be played. (avoid duplicate moves)
 * the idea is taken from the program "floodit" by Aaron and Simon Puchert,
 * which can be found at <a>https://github.com/aaronpuchert/floodit</a>
 * @param nextColor
 * @return
 */
private boolean canPlay(final byte nextColor, final List<ColorArea> nextColorNeighbors) {
    final byte currColor = this.solution[this.solutionSize];
    // did the previous move add any new "nextColor" neighbors?
    boolean newNext = false;
next:   for (final ColorArea nextColorNeighbor : nextColorNeighbors) {
        for (final ColorArea prevNeighbor : nextColorNeighbor.getNeighborsArray()) {
            if ((prevNeighbor.getColor() != currColor) && this.flooded.contains(prevNeighbor)) {
                continue next;
            }
        }
        newNext = true;
        break next;
    }
    if (!newNext) {
        if (nextColor < currColor) {
            return false;
        } else {
            // should nextColor have been played before currColor?
            for (final ColorArea nextColorNeighbor : nextColorNeighbors) {
                for (final ColorArea prevNeighbor : nextColorNeighbor.getNeighborsArray()) {
                    if ((prevNeighbor.getColor() == currColor) && !this.flooded.contains(prevNeighbor)) {
                        return false;
                    }
                }
            }
            return true;
        }
    } else {
        return true;
    }
}

/**
 * try to re-use the given node or create a new one
 * and then play the given color in the result node.
 * @param nextColor
 * @param recycleNode
 * @return
 */
public AStarNode copyAndPlay(final byte nextColor, final AStarNode recycleNode, final Board board) {
    final List<ColorArea> nextColorNeighbors = new ArrayList<ColorArea>(128);  // constant, arbitrary initial capacity
    final ColorAreaSet.FastIteratorColorAreaId iter = this.neighbors.fastIteratorColorAreaId();
    int nextId;
    while ((nextId = iter.nextOrNegative()) >= 0) {
        final ColorArea nextColorNeighbor = board.getColorArea4Id(nextId);
        if (nextColorNeighbor.getColor() == nextColor) {
            nextColorNeighbors.add(nextColorNeighbor);
        }
    }
    if (!this.canPlay(nextColor, nextColorNeighbors)) {
        return null;
    } else {
        final AStarNode result;
        if (null == recycleNode) {
            result = new AStarNode(this);
        } else {
            // copy - compare copy constructor
            result = recycleNode;
            result.flooded.copyFrom(this.flooded);
            result.neighbors.copyFrom(this.neighbors);
            System.arraycopy(this.solution, 0, result.solution, 0, this.solutionSize + 1);
            result.solutionSize = this.solutionSize;
            //result.estimatedCost = this.estimatedCost;  // not necessary to copy
        }
        // play - compare method play()
        for (final ColorArea nextColorNeighbor : nextColorNeighbors) {
            result.flooded.add(nextColorNeighbor);
            result.neighbors.addAll(nextColorNeighbor.getNeighborsIdArray());
        }
        result.neighbors.removeAll(result.flooded);
        result.solution[++result.solutionSize] = nextColor;
        return result;
    }
}

2
ยินดีต้อนรับสู่ PPCG! คุณสามารถใส่รหัสที่เกี่ยวข้องสำหรับตัวแก้ปัญหาในคำตอบได้ด้วยตัวเองเพื่อที่ว่าจะได้มีอยู่ในตัวเอง gitub repo ของคุณควรจะย้ายหรือลงไปหรือไม่?
Martin Ender

เพิ่มส่วนที่เกี่ยวข้องมากที่สุดของรหัสที่นี่: การใช้งาน "อัลกอริทึม Puchert A *" ของฉัน (ข้อความที่ตัดตอนมานี้ไม่ได้บรรจุในตัวเองและไม่สามารถรวบรวมได้ตามที่เป็นอยู่)
smack42

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

15

C # - 2,098,382 ขั้นตอน

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

มีวิธีการที่จะทำให้ดียิ่งขึ้นไปอีก ฉันคิดว่าจะไปตามขั้นตอน 2M อาจเป็นไปได้

มันใช้เวลาประมาณ7 hoursเพื่อสร้างผลลัพธ์ นี่คือไฟล์ txt พร้อมโซลูชันทั้งหมดในกรณีที่มีคนต้องการตรวจสอบ: results.zip

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace FloodPaintAI
{
    class Node
    {   
        public byte Value;             //1-6
        public int Index;              //unique identifier, used for easily deepcopying the graph
        public List<Node> Neighbours;  
        public List<Tuple<int, int>> NeighboursPositions; //used by BuildGraph() 

        public int Depth;    //used by GetSumDistances() 
        public bool Checked; // 

        public Node(byte value, int index)
        {
            Value = value;      
            Index = index;          
        }

        public Node(Node node)
        {           
            Value = node.Value; 
            Index = node.Index;                     
        }
    }

    class Board
    {
        private const int SIZE = 19;
        private const int STARTPOSITION = 9;

        public Node Root;         //root of graph. each node is a set of contiguous, same color square
        public List<Node> Nodes;  //all nodes in the graph, used for deep copying


        public int EstimatedCost; //estimated cost, used by A* Pathfinding
        public List<byte> Solution;

        public Board(StreamReader input)
        {                   
            byte[,] board = new byte[SIZE, SIZE];
            for(int j = 0 ; j < SIZE ; j++)
            {
                string line = input.ReadLine();
                for(int i = 0 ; i < SIZE ; i++)         
                {                                       
                    board[j, i] = byte.Parse(line[i].ToString());
                }               
            }
            Solution = new List<byte>();
            BuildGraph(board);  
        }

        public Board(Board boardToCopy)
        {               
            //copy the graph            
            Nodes = new List<Node>(boardToCopy.Nodes.Count);
            foreach(Node nodeToCopy in boardToCopy.Nodes)
            {
                Node node = new Node(nodeToCopy);
                Nodes.Add(node);
            }

            //copy "Neighbours" property
            for(int i = 0 ; i < boardToCopy.Nodes.Count ; i++)
            {
                Node node = Nodes[i];
                Node nodeToCopy = boardToCopy.Nodes[i];

                node.Neighbours = new List<Node>(nodeToCopy.Neighbours.Count);
                foreach(Node neighbour in nodeToCopy.Neighbours)
                {
                    node.Neighbours.Add(Nodes[neighbour.Index]);
                }
            }

            Root = Nodes[boardToCopy.Root.Index];
            EstimatedCost = boardToCopy.EstimatedCost;          
            Solution = new List<byte>(boardToCopy.Solution);            
        }

        private void BuildGraph(byte[,] board)
        {                       
            int[,] nodeIndexes = new int[SIZE, SIZE];
            Nodes = new List<Node>();

            //check how much sets we have (1st pass)
            for(int j = 0 ; j < SIZE ; j++)
            {
                for(int i = 0 ; i < SIZE ; i++)         
                {               
                    if(nodeIndexes[j, i] == 0) //not already visited                    
                    {
                        Node newNode = new Node(board[j, i], Nodes.Count);                      
                        newNode.NeighboursPositions = new List<Tuple<int, int>>();
                        Nodes.Add(newNode);

                        BuildGraphFloodFill(board, nodeIndexes, newNode, i, j, board[j, i]);
                    }
                }       
            }

            //set neighbours and root (2nd pass)
            foreach(Node node in Nodes)
            {
                node.Neighbours = new List<Node>();
                node.Neighbours.AddRange(node.NeighboursPositions.Select(x => nodeIndexes[x.Item2, x.Item1]).Distinct().Select(x => Nodes[x - 1]));
                node.NeighboursPositions = null;                
            }
            Root = Nodes[nodeIndexes[STARTPOSITION, STARTPOSITION] - 1];            
        }

        private void BuildGraphFloodFill(byte[,] board, int[,] nodeIndexes, Node node, int startx, int starty, byte floodvalue)
        {
            Queue<Tuple<int, int>> queue = new Queue<Tuple<int, int>>();
            queue.Enqueue(new Tuple<int, int>(startx, starty));

            while(queue.Count > 0)
            {
                Tuple<int, int> position = queue.Dequeue();
                int x = position.Item1;
                int y = position.Item2;

                if(x >= 0 && x < SIZE && y >= 0 && y < SIZE)
                {
                    if(nodeIndexes[y, x] == 0 && board[y, x] == floodvalue)
                    {
                        nodeIndexes[y, x] = node.Index + 1;

                        queue.Enqueue(new Tuple<int, int>(x + 1, y));
                        queue.Enqueue(new Tuple<int, int>(x - 1, y));
                        queue.Enqueue(new Tuple<int, int>(x, y + 1));
                        queue.Enqueue(new Tuple<int, int>(x, y - 1));                                           
                    }               
                    if(board[y, x] != floodvalue)
                        node.NeighboursPositions.Add(position);                         
                }       
            }
        }

        public int GetEstimatedCost()
        {       
            Board current = this;

            //copy current board and play the best color until the end.
            //number of moves required to go the end is the heuristic
            //estimated cost = current cost + heuristic
            while(!current.IsSolved())
            {
                int minSumDistance = int.MaxValue;
                Board minBoard = null;

                //find color which give the minimum sum of distance from root to each other node
                foreach(byte i in current.Root.Neighbours.Select(x => x.Value).Distinct())
                {
                    Board copy = new Board(current);
                    copy.Play(i);                   

                    int distance = copy.GetSumDistances();                  

                    if(distance < minSumDistance)
                    {
                        minSumDistance = distance;
                        minBoard = copy;
                    }
                }
                current = minBoard;
            }           
            return current.Solution.Count;
        }

        public int GetSumDistances()
        {
            //get sum of distances from root to each other node, using BFS
            int sumDistances = 0;           

            //reset marker
            foreach(Node n in Nodes)
            {
                n.Checked = false;                                  
            }

            Queue<Node> queue = new Queue<Node>();
            Root.Checked = true;
            Root.Depth = 0; 
            queue.Enqueue(Root);

            while(queue.Count > 0)
            {
                Node current = queue.Dequeue();                             
                foreach(Node n in current.Neighbours)
                {
                    if(!n.Checked)          
                    {                                   
                        n.Checked = true;                                               
                        n.Depth = current.Depth + 1;
                        sumDistances += n.Depth;            
                        queue.Enqueue(n);   
                    }               
                }
            }
            return sumDistances;
        }       

        public void Play(byte value)            
        {
            //merge root node with other neighbours nodes, if color is matching
            Root.Value = value;
            List<Node> neighboursToRemove = Root.Neighbours.Where(x => x.Value == value).ToList();
            List<Node> neighboursToAdd = neighboursToRemove.SelectMany(x => x.Neighbours).Except((new Node[] { Root }).Concat(Root.Neighbours)).ToList();

            foreach(Node n in neighboursToRemove)
            {
                foreach(Node m in n.Neighbours)
                {
                    m.Neighbours.Remove(n);
                }
                Nodes.Remove(n);
            }   

            foreach(Node n in neighboursToAdd)
            {
                Root.Neighbours.Add(n);         
                n.Neighbours.Add(Root); 
            }           

            //re-synchronize node index
            for(int i = 0 ; i < Nodes.Count ; i++)
            {
                Nodes[i].Index = i;
            }           
            Solution.Add(value);
        }

        public bool IsSolved()
        {           
            //return Nodes.Count == 1;
            return Root.Neighbours.Count == 0;  
        }           
    }


    class Program
    {       
        public static List<byte> Solve(Board input)
        {
            //A* Pathfinding            
            LinkedList<Board> open = new LinkedList<Board>();       
            input.EstimatedCost = input.GetEstimatedCost();
            open.AddLast(input);            

            while(open.Count > 0)
            {                       
                Board current = open.First.Value;
                open.RemoveFirst();

                if(current.IsSolved())
                {
                    return current.Solution;                
                }
                else
                {
                    //play all neighbours nodes colors
                    foreach(byte i in current.Root.Neighbours.Select(x => x.Value).Distinct())
                    {                       
                        Board newBoard = new Board(current);
                        newBoard.Play(i);           
                        newBoard.EstimatedCost = newBoard.GetEstimatedCost();   

                        //insert board to open list
                        bool inserted = false;
                        for(LinkedListNode<Board> node = open.First ; node != null ; node = node.Next)
                        {                               
                            if(node.Value.EstimatedCost > newBoard.EstimatedCost)
                            {
                                open.AddBefore(node, newBoard);
                                inserted = true;
                                break;
                            }
                        }       
                        if(!inserted)
                            open.AddLast(newBoard);                                                 
                    }   
                }   
            }
            throw new Exception(); //no solution found, impossible
        }   

        public static void Main(string[] args)
        {                   
            using (StreamReader sr = new StreamReader("floodtest"))
            {   
                while(!sr.EndOfStream)
                {                               
                    List<Board> boards = new List<Board>();
                    while(!sr.EndOfStream && boards.Count < 100)
                    {
                        Board board = new Board(sr);                        
                        sr.ReadLine(); //skip empty line
                        boards.Add(board);
                    }                                           
                    List<byte>[] solutions = new List<byte>[boards.Count];                                          
                    Parallel.For(0, boards.Count, i => 
                    {                               
                        solutions[i] = Solve(boards[i]); 
                    });                                         
                    foreach(List<byte> solution in solutions)
                    {
                        Console.WriteLine(string.Join(string.Empty, solution));                                             
                    }       
                }               
            }
        }
    }
}

รายละเอียดเพิ่มเติมเกี่ยวกับวิธีการทำงาน:

มันใช้อัลกอริทึมA * Pathfinding

heuristicอะไรคือสิ่งที่ยากคือการหาที่ดี ถ้าheuristicมันประเมินค่าต่ำไปมันจะทำงานเหมือนDijkstra algorithm เกือบเหมือนกันและเนื่องจากความซับซ้อนของบอร์ด 19x19 ที่มี 6 สีมันจะทำงานตลอดไป ถ้ามันประเมินค่าสูงเกินไปมันจะมาบรรจบกันอย่างรวดเร็วกับวิธีแก้ปัญหา แต่จะไม่ให้สิ่งที่ดีเลย การค้นหาความสมบูรณ์แบบheuristicที่ให้จำนวนขั้นตอนที่แน่นอนในการเข้าถึงโซลูชันจะดีที่สุด (มันจะเร็วและจะให้ทางออกที่ดีที่สุด) มันเป็นไปไม่ได้ (นอกเสียจากว่าฉันผิด) มันจำเป็นต้องแก้ปัญหากระดานก่อนในขณะที่นี่คือสิ่งที่คุณพยายามทำจริง (ปัญหาไก่และไข่)

ฉันลองหลาย ๆ อย่างนี่คือสิ่งที่ใช้ได้ผลกับheuristic:

  • ฉันสร้างกราฟของบอร์ดปัจจุบันเพื่อประเมิน แต่ละnodeชุดเป็นตัวแทนของชุดสี่เหลี่ยมที่มีสีต่อเนื่องกัน เมื่อใช้อย่างนั้นgraphฉันสามารถคำนวณระยะทางที่น้อยที่สุดจากกึ่งกลางถึงโหนดอื่น ๆ ได้อย่างง่ายดาย ตัวอย่างเช่นระยะทางจากกึ่งกลางถึงด้านบนซ้ายจะเป็น 10 เนื่องจากอย่างน้อย 10 สีแยกกัน
  • สำหรับการคำนวณheuristic: ฉันเล่นกระดานปัจจุบันจนกระทั่งจบ สำหรับแต่ละขั้นตอนฉันเลือกสีที่จะลดผลรวมของระยะทางจากรูตไปยังโหนดอื่นทั้งหมด
  • heuristicจำนวนของการเคลื่อนไหวที่จำเป็นในการถึงจุดสิ้นสุดที่เป็น

  • Estimated cost(ใช้โดย A *) = moves so far+heuristic

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

ฉันสามารถรับการปรับปรุงความเร็วได้อย่างมากโดยใช้กราฟเพื่อดำเนินการทุกอย่าง ที่จุดเริ่มต้นฉันมี2-dimensionอาเรย์ ฉันท่วมมันและคำนวณกราฟใหม่เมื่อจำเป็น (เช่นเพื่อคำนวณฮิวริสติก) ตอนนี้ทุกอย่างเสร็จสิ้นโดยใช้กราฟซึ่งคำนวณเฉพาะตอนเริ่มต้นเท่านั้น เพื่อจำลองขั้นตอนไม่จำเป็นต้องเติมน้ำท่วมอีกต่อไปฉันรวมโหนดแทน มันเร็วกว่ามาก


2
โปรดอย่าใช้code blocksเพื่อเน้นข้อความ เรามีตัวเอียงและตัวหนาสำหรับเรื่องนี้
Nic Hartley

10

Python - 10,800,000 ขั้นตอน

ในฐานะที่เป็นโซลูชันอ้างอิงล่าสุดให้พิจารณาลำดับนี้:

print "123456" * 18

การขี่จักรยานผ่านทุกสีnหมายความว่าทุกnขั้นตอนออกไปจะรับประกันได้ว่ามีสีเดียวกับจัตุรัสกลาง ทุกจัตุรัสอยู่ห่างจากศูนย์กลางมากที่สุด 18 ขั้นดังนั้นรอบ 18 จะรับประกันว่าสี่เหลี่ยมทั้งหมดเป็นสีเดียวกัน เป็นไปได้มากว่าโปรแกรมจะเสร็จสิ้นในเวลาน้อยกว่านั้น แต่โปรแกรมไม่จำเป็นต้องหยุดทันทีที่สี่เหลี่ยมทั้งหมดมีสีเดียวกัน มันเป็นประโยชน์มากกว่าที่จะทำเช่นนั้น ขั้นตอนคงที่นี้คือ 108 ขั้นตอนต่อกรณีทดสอบรวมเป็น 10,800,000


กำลังดุร้ายวิธีจริงจัง ... โจฉันคิดว่าคุณมีประสบการณ์มากขึ้นที่จะรู้ว่าดีกว่าเพื่อน?
WallyWest

2
มันไม่ได้หมายความว่าเป็นรายการที่ร้ายแรง หมายเหตุที่ผมใส่มันขึ้นมาโดยเฉพาะเป็นวิธีแก้ปัญหาที่จะทำหน้าที่เป็นที่รับทั้งหมดในสถานที่สุดท้าย รายการใด ๆ ที่ร้ายแรงจะมีคะแนนมากต่ำกว่านี้
Joe Z.

ไม่ควรมีช่องว่าง? ในขณะที่แทนของการแก้ปัญหาในปัจจุบันของคุณซึ่งจะช่วยให้1 2 3 4 5 6 ... 123456...
user80551

1
จะเป็นอัลกอริทึมที่ดีที่สุดสำหรับการเขียนโค้ดกอล์ฟ (ในภาษาอื่น ๆ แม้ว่า "print" จะมีตัวอักษรมากเกินไป)
Cruncher

1
ฉันยังไม่คิดว่ากรณีที่เลวร้ายที่สุดของ 18 ขั้นตอนแม้จะเป็นไปได้ แต่แน่นอนเราไม่ทราบว่ามีกรณีที่ไม่เลวร้ายยิ่งกว่านั้นก็เพื่อให้งานนี้แน่นอน :)
Cruncher

8

Java - 2,480,714 ขั้นตอน

ฉันทำผิดเล็กน้อยมาก่อน (ฉันใส่ประโยคสำคัญหนึ่งประโยคต่อหน้าวงวนแทนในวงวน:

import java.io.*;

public class HerjanPaintAI {

    BufferedReader r;
    String[] map = new String[19];
    char[][] colors = new char[19][19];
    boolean[][] reached = new boolean[19][19], checked = new boolean[19][19];
    int[] colorCounter = new int[6];
    String answer = "";
    int mapCount = 0, moveCount = 0;

    public HerjanPaintAI(){
        nextMap();

        while(true){

            int bestMove = nextRound();
            answer += bestMove;
            char t = Character.forDigit(bestMove, 10);
            for(int x = 0; x < 19; x++){
                for(int y = 0; y < 19; y++){
                    if(reached[x][y]){
                        colors[x][y] = t;
                    }else if(checked[x][y]){
                        if(colors[x][y] == t){
                            reached[x][y] = true;
                        }
                    }
                }
            }

            boolean gameOver = true;
            for(int x = 0; x < 19; x++){
                for(int y = 0; y < 19; y++){
                    if(!reached[x][y]){
                        gameOver = false;
                        break;
                    }
                }
            }

            for(int x = 0; x < 19; x++){
                for(int y = 0; y < 19; y++){
                    checked[x][y] = false;
                }
            }
            for(int i = 0; i < 6; i++)
                colorCounter[i] = 0;

            if(gameOver)
                nextMap();
        }
    }

    int nextRound(){
        for(int x = 0; x < 19; x++){
            for(int y = 0; y < 19; y++){
                if(reached[x][y]){//check what numbers are next to the reached numbers...
                    check(x, y);
                }
            }
        }

        int[] totalColorCount = new int[6];
        for(int x = 0; x < 19; x++){
            for(int y = 0; y < 19; y++){
                totalColorCount[Character.getNumericValue(colors[x][y])-1]++;
            }
        }

        for(int i = 0; i < 6; i++){
            if(totalColorCount[i] != 0 && totalColorCount[i] == colorCounter[i]){//all of this color can be reached
                return i+1;
            }
        }

        int index = -1, number = 0;
        for(int i = 0; i < 6; i++){
            if(colorCounter[i] > number){
                index = i;
                number = colorCounter[i];
            }
        }

        return index+1;
    }

    void check(int x, int y){
        if(x<18)
            handle(x+1, y, x, y);
        if(x>0)
            handle(x-1, y, x, y);
        if(y<18)
            handle(x, y+1, x, y);
        if(y>0)
            handle(x, y-1, x, y);
    }

    void handle(int x2, int y2, int x, int y){
        if(!reached[x2][y2] && !checked[x2][y2]){
            checked[x2][y2] = true;
            if(colors[x2][y2] == colors[x][y]){
                reached[x2][y2] = true;
                check(x2, y2);
            }else{
                colorCounter[Character.getNumericValue(colors[x2][y2])-1]++;
                checkAround(x2, y2);
            }
        }
    }

    void checkAround(int x2, int y2){
        if(x2<18)
            handleAround(x2+1, y2, x2, y2);
        if(x2>0)
            handleAround(x2-1, y2, x2, y2);
        if(y2<18)
            handleAround(x2, y2+1, x2, y2);
        if(y2>0)
            handleAround(x2, y2-1, x2, y2);
    }

    void handleAround(int x2, int y2, int x, int y){
        if(!reached[x2][y2] && !checked[x2][y2]){
            if(colors[x2][y2] == colors[x][y]){
                checked[x2][y2] = true;
                colorCounter[Character.getNumericValue(colors[x2][y2])-1]++;
                checkAround(x2, y2);
            }
        }
    }

    void nextMap(){
        moveCount += answer.length();
        System.out.println(answer);
        answer = "";

        for(int x = 0; x < 19; x++){
            for(int y = 0; y < 19; y++){
                reached[x][y] = false;
            }
        }

        reached[9][9] = true;

        try {
            if(r == null)
                r = new BufferedReader(new FileReader("floodtest"));

            for(int i = 0; i < 19; i++){
                map[i] = r.readLine();
            }
            r.readLine();//empty line

            if(map[0] == null){
                System.out.println("Maps solved: " + mapCount + " Steps: " + moveCount);
                r.close();
                System.exit(0);
            }
        } catch (Exception e) {e.printStackTrace();}

        mapCount++;

        for(int x = 0; x < 19; x++){
            colors[x] = map[x].toCharArray();
        }
    }

    public static void main(String[] a){
        new HerjanPaintAI();
    }
}

การดำเนินการนี้ใช้เวลานานเท่าใด
alexander-brett

@ ali0sha พีซีของฉันใช้เวลาไม่ถึงครึ่งนาที
Herjan

อึอึ ฉันทำงานมาแล้วครึ่งชั่วโมง ...
alexander-brett

ไม่จำเป็นต้องเล่นกอล์ฟ
Joe Z.

1
@ m.buettner พูดของพญามารเป็นใครสักคนที่ไม่ผูกแก้ปัญหานี้ (และมีรหัสสั้น) สามชั่วโมงหลังจากที่คุณบอกว่า
Joe Z.

5

C - 2,075,452

ฉันรู้ว่าฉันมางานสายมาก แต่ฉันเห็นความท้าทายนี้และอยากจะไป

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

uint64_t rand_state;

uint64_t rand_u64(void) {
    return (rand_state = rand_state * 6364136223846793005ULL + 1442695040888963407ULL);
}

uint64_t better_rand_u64(void) {
    uint64_t r = rand_u64();
    r ^= ((r >> 32) >> (r >> 60));
    return r + 1442695040888963407ULL;
}

uint32_t rand_u32(void) {return rand_u64() >> 32;}

float normal(float mu, float sigma) {
    uint64_t t = 0;
    for (int i = 0; i < 6; i++) {
        uint64_t r = rand_u64();
        uint32_t a = r;
        uint32_t b = r >> 32;
        t += a;
        t += b;
    }
    return ((float)t / (float)UINT32_MAX - 6) * sigma + mu;
}

typedef struct {
    uint8_t x;
    uint8_t y;
} Position;

#define ncolors 6
#define len 19
#define cells (len * len)
#define max_steps (len * (ncolors - 1))
#define center_x 9
#define center_y 9
#define center ((Position){center_x, center_y})

uint64_t zobrist_table[len][len];

void init_zobrist() {
    for (int y = 0; y < len; y++) {
        for (int x = 0; x < len; x++) {
            zobrist_table[y][x] = better_rand_u64();
        }
    }
}

typedef struct {
    uint64_t hash;
    uint8_t grid[len][len];
    bool interior[len][len];
    int boundary_size;
    Position boundary[cells];
} Grid;


void transition(Grid* grid, uint8_t color, int* surrounding_counts) {
    int i = 0;
    while (i < grid->boundary_size) {
        Position p = grid->boundary[i];
        uint8_t x = p.x;
        uint8_t y = p.y;
        bool still_boundary = false;
        for (int dx = -1; dx <= 1; dx++) {
            for (int dy = -1; dy <= 1; dy++) {
                if (!(dx == 0 || dy == 0)) {
                    continue;
                }
                int8_t x1 = x + dx;
                if (!(0 <= x1 && x1 < len)) {
                    continue;
                }
                int8_t y1 = y + dy;
                if (!(0 <= y1 && y1 < len)) {
                    continue;
                }
                if (grid->interior[y1][x1]) {
                    continue;
                }
                uint8_t color1 = grid->grid[y1][x1];
                if (color1 == color) {
                    grid->boundary[grid->boundary_size++] = (Position){x1, y1};
                    grid->interior[y1][x1] = true;
                    grid->hash ^= zobrist_table[y1][x1];
                } else {
                    surrounding_counts[color1]++;
                    still_boundary = true;
                }
            }
        }
        if (still_boundary) {
            i += 1;
        } else {
            grid->boundary[i] = grid->boundary[--grid->boundary_size]; 
        }
    }
}

void reset_grid(Grid* grid, int* surrounding_counts) {
    grid->hash = 0;
    memset(surrounding_counts, 0, ncolors * sizeof(int)); 
    memset(&grid->interior, 0, sizeof(grid->interior));
    grid->interior[center_y][center_x] = true;
    grid->boundary_size = 0;
    grid->boundary[grid->boundary_size++] = center; 
    transition(grid, grid->grid[center_y][center_x], surrounding_counts);
}

bool load_grid(FILE* fp, Grid* grid) {
    memset(grid, 0, sizeof(*grid));
    char buf[19 + 2];
    size_t row = 0;
    while ((fgets(buf, sizeof(buf), fp)) && row < 19) {
        if (strlen(buf) != 20) {
            break;
        }
        for (int i = 0; i < 19; i++) {
            if (!('1' <= buf[i] && buf[i] <= '6')) {
                return false;
            }
            grid->grid[row][i] = buf[i] - '1';
        }
        row++;
    }
    return row == 19;
}

typedef struct Node Node;

struct Node {
    uint64_t hash;
    float visit_counts[ncolors];
    float mean_cost[ncolors];
    float sse[ncolors];
};

#define iters 15000
#define pool_size 32768
#define pool_nodes (pool_size + 100)
#define pool_mask (pool_size - 1)

Node pool[pool_nodes];

void init_node(Node* node, uint64_t hash, int* surrounding_counts) {
    node->hash = hash;
    for (int i = 0; i < ncolors; i++) {
        if (surrounding_counts[i]) {
            node->visit_counts[i] = 1;
            node->mean_cost[i] = 20;
            node->sse[i] = 400;
        }
    }
}

Node* lookup_node(uint64_t hash) {
    size_t index = hash & pool_mask;
    for (int i = index;; i++) {
        uint64_t h = pool[i].hash;
        if (h == hash || !h) {
            return pool + i;
        }
    }
}

int rollout(Grid* grid, int* surrounding_counts, char* solution) {
    for (int i = 0;; i++) {
        int nonzero = 0;
        uint8_t colors[6];
        for (int i = 0; i < ncolors; i++) {
            if (surrounding_counts[i]) {
                colors[nonzero++] = i;
            }
        }
        if (!nonzero) {
            return i;
        }
        uint8_t color = colors[rand_u32() % nonzero]; 
        *(solution++) = color;
        assert(grid->boundary_size);
        memset(surrounding_counts, 0, 6 * sizeof(int));
        transition(grid, color, surrounding_counts);
    }
}

int simulate(Node* node, Grid* grid, int depth, char* solution) {
    float best_cost = INFINITY;
    uint8_t best_color = 255;
    for (int color = 0; color < ncolors; color++) {
        float n = node->visit_counts[color];
        if (node->visit_counts[color] == 0) {
            continue;
        }
        float sigma = sqrt(node->sse[color] / (n * n));
        float cost = node->mean_cost[color];
        cost = normal(cost, sigma);
        if (cost < best_cost) {
            best_color = color;
            best_cost = cost;
        }
    }
    if (best_color == 255) {
        return 0;
    }
    *solution++ = best_color;
    int score;
    int surrounding_counts[ncolors] = {0};
    transition(grid, best_color, surrounding_counts);
    Node* child = lookup_node(grid->hash);
    if (!child->hash) {
        init_node(child, grid->hash, surrounding_counts);
        score = rollout(grid, surrounding_counts, solution);
    } else {
        score = simulate(child, grid, depth + 1, solution);
    }
    score++;
    float n1 = ++node->visit_counts[best_color];
    float u0 = node->mean_cost[best_color];
    float u1 = node->mean_cost[best_color] = u0 + (score - u0) / n1;
    node->sse[best_color] += (score - u0) * (score - u1);
    return score;
}

int main(void) {
    FILE* fp;
    if (!(fp = fopen("floodtest", "r"))) {
        return 1;
    }
    Grid grid;
    init_zobrist();
    while (load_grid(fp, &grid)) {

        memset(pool, 0, sizeof(pool));
        int surrounding_counts[ncolors] = {0};

        reset_grid(&grid, surrounding_counts);
        Node root = {0};

        init_node(&root, grid.hash, surrounding_counts);

        char solution[max_steps] = {0};
        char best_solution[max_steps] = {0};

        int min_score = INT_MAX;

        uint64_t prev_hash = 0;
        uint64_t hash = 0;
        int same_count = 0;

        for (int iter = 0; iter < iters; iter++) {
            reset_grid(&grid, surrounding_counts);
            int score = simulate(&root, &grid, 1, solution);
            if (score < min_score) {
                min_score = score;
                memcpy(best_solution, solution, score);
            }
            hash = 0;
            for (int i = 0; i < score; i++) {
                hash ^= zobrist_table[i%len][(int)solution[i]];
            }
            if (hash == prev_hash) {
                same_count++;
                if (same_count >= 10) {
                    break;
                }
            } else {
                same_count = 0;
                prev_hash = hash;
            }
        }
        int i;
        for (i = 0; i < min_score; i++) {
            best_solution[i] += '1';
        }
        best_solution[i++] = '\n';
        best_solution[i++] = '\0';
        printf(best_solution);
        fflush(stdout);
    }
    return 0;
}

อัลกอริทึมนั้นมาจากการค้นหาต้นไม้ Monte-Carlo ด้วยการสุ่มตัวอย่าง Thompson และตารางการขนย้ายเพื่อลดพื้นที่การค้นหา ใช้เวลาประมาณ 12 ชั่วโมงในเครื่องของฉัน หากคุณต้องการที่จะตรวจสอบผลที่คุณสามารถค้นหาได้ที่https://dropfile.to/pvjYDMV


ผู้ใช้smack42แสดงให้เห็นการเปลี่ยนแปลงhash ^= zobrist_table[i][(int)solution[i]];ในการhash ^= zobrist_table[i%len][(int)solution[i]];ที่จะแก้ไขความผิดพลาดของโปรแกรม
Stephen

@ ขั้นตอนที่ฉันไม่เห็นว่าคะแนนสามารถมากกว่า len คุณมีอินพุตที่ทำให้เกิดข้อผิดพลาดนี้หรือไม่ คุณมีลิงค์ไปสู่บทสนทนาของคุณกับ smak42 หรือไม่? แม้ว่าจะไม่สามารถพังได้ แต่ฉันคิดว่าไม่มีอันตรายใด ๆ ที่จะเกิดขึ้นกับการใช้รหัสที่ไม่ปลอดภัย
user1502040

ไม่ขอโทษมันอยู่ในการแก้ไขที่แนะนำ นี่คือความคิดเห็น: codegolf.stackexchange.com/review/suggested-edits/42008
Stephen

+1 สำหรับการตีฉันที่นี่ แต่ระวังอาจจะมีการปรับปรุงบางอย่างที่จะมา;)
tigrou

4

ชวา - 2,434,108 2,588,847 ขั้นตอน

ขณะนี้ได้รับรางวัล (~ 46K ล่วงหน้าจาก Herjan) ตั้งแต่ 4/26

Welp ดังนั้น MrBackend ไม่เพียง แต่เอาชนะฉันเท่านั้น แต่ฉันพบข้อบกพร่องซึ่งทำให้ได้คะแนนที่ดี ตอนนี้ได้รับการแก้ไขแล้ว (ยังอยู่ในเครื่องยืนยัน! Ack) แต่น่าเสียดายที่ฉันไม่มีเวลาที่จะลองและสวมมงกุฎคืนนี้ จะลองใหม่ในภายหลัง

นี่เป็นวิธีแก้ปัญหาอื่นของฉัน แต่แทนที่จะทาสีด้วยสีที่พบได้บ่อยที่สุดที่ขอบเติมมันจะใช้สีที่จะทำให้เห็นขอบที่มีสี่เหลี่ยมที่อยู่ติดกันหลายสี เรียกว่า LookAheadPainter ฉันจะตีกอล์ฟในภายหลังหากจำเป็น

import java.io.*;
import java.util.*;

public class LookAheadPainter {

    static final boolean PRINT_FULL_OUTPUT = true;

    public static void main(String[] a) throws IOException {

        int totalSteps = 0, numSolved = 0;

        char[] board = new char[361];
        Scanner s = new Scanner(new File("floodtest"));
        long startTime = System.nanoTime();

        caseloop: while (s.hasNextLine()) {
            for (int l = 0; l < 19; l++) {
                String line = s.nextLine();
                if (line.isEmpty())
                    continue caseloop;
                System.arraycopy(line.toCharArray(), 0, board, l * 19, 19);
            }

            List<Character> colorsUsed = new ArrayList<>();

            for (;;) {

                FillResult fill = new FillResult(board, board[180], (char) 48, null);

                if (fill.nodesFilled.size() == 361)
                    break;

                int[] branchSizes = new int[7];

                for (int i = 1; i < 7; i++) {
                    List<Integer> seeds = new ArrayList<>();
                    for (Integer seed : fill.edges)
                        if (board[seed] == i + 48)
                            seeds.add(seed);

                    branchSizes[i] = new FillResult(fill.filledBoard, (char) (i + 48), (char) 48, seeds).nodesFilled.size();
                }

                int maxSize = 0;
                char bestColor = 0;

                for (int i = 1; i < 7; i++)
                    if (branchSizes[i] > maxSize) {
                        maxSize = branchSizes[i];
                        bestColor = (char) (i + 48);
                    }

                for (int i : fill.nodesFilled)
                    board[i] = bestColor;

                colorsUsed.add(bestColor);
                totalSteps++;
            }
            numSolved++;

            if (PRINT_FULL_OUTPUT) {
                if (numSolved % 1000 == 0)
                    System.out.println("Solved: " + numSolved); // So you know it's working
                String out = "";
                for (Character c : colorsUsed)
                    out += c;
                System.out.println(out);
            }

        }
        s.close();
        System.out.println("\nTotal steps to solve all cases: " + totalSteps);
        System.out.printf("\nSolved %d test cases in %.2f seconds", numSolved, (System.nanoTime() - startTime) / 1000000000.);
    }

    private static class FillResult {

        Set<Integer> nodesFilled, edges;
        char[] filledBoard;

        FillResult(char[] board, char target, char replacement, List<Integer> seeds) {
            Stack<Integer> nodes = new Stack<>();
            nodesFilled = new HashSet<>();
            edges = new HashSet<>();

            if (seeds == null)
                nodes.push(180);
            else
                for (int i : seeds)
                    nodes.push(i);

            filledBoard = new char[361];
            System.arraycopy(board, 0, filledBoard, 0, 361);

            while (!nodes.empty()) {
                int n = nodes.pop();
                if (n < 0 || n > 360)
                    continue;
                if (filledBoard[n] == target) {
                    filledBoard[n] = replacement;
                    nodesFilled.add(n);
                    if (n % 19 > 0)
                        nodes.push(n - 1);
                    if (n % 19 < 18)
                        nodes.push(n + 1);
                    if (n / 19 > 0)
                        nodes.push(n - 19);
                    if (n / 19 < 18)
                        nodes.push(n + 19);
                } else
                    edges.add(n);
            }
        }
    }
}

แก้ไข: ฉันเขียนตัวตรวจสอบรู้สึกอิสระที่จะใช้มันคาดว่าไฟล์ steps.txt ที่มีขั้นตอนที่โปรแกรมของคุณส่งออกเช่นเดียวกับไฟล์ floodtest: แก้ไขแก้ไข: (ดู OP)

หากใครพบปัญหากรุณารายงานมาให้ฉัน!


นีซ, พิซซ่า! และตัวตรวจสอบนั้นเป็นสิ่งที่ฉลาดอย่างแน่นอน! OP ควรทำบางสิ่งเช่นนี้ / โปรแกรมควบคุม (ซึ่งจะแก้ไขปัญหาได้มากมาย)
Herjan

3

C - 2,480,714 ขั้นตอน

ยังไม่ดีที่สุด แต่ตอนนี้เร็วขึ้นและทำคะแนนได้ดี

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

char map[19][19], reach[19][19];
int reachsum[6], totalsum[6];

bool loadmap(FILE *fp)
{
    char buf[19 + 2];
    size_t row = 0;

    while (fgets(buf, sizeof buf, fp) && row < 19) {
        if (strlen(buf) != 20)
            break;
        memcpy(map[row++], buf, 19);
    }
    return row == 19;
}

void calcreach(bool first, size_t row, size_t col);
void check(char c, bool first, size_t row, size_t col)
{
    if (map[row][col] == c)
        calcreach(first, row, col);
    else if (first)
        calcreach(false, row, col);
}

void calcreach(bool first, size_t row, size_t col)
{
    char c = map[row][col];

    reach[row][col] = c;
    reachsum[c - '1']++;
    if (row < 18 && !reach[row + 1][col])
        check(c, first, row + 1, col);
    if (col < 18 && !reach[row][col + 1])
        check(c, first, row, col + 1);
    if (row > 0 && !reach[row - 1][col])
        check(c, first, row - 1, col);
    if (col > 0 && !reach[row][col - 1])
        check(c, first, row, col - 1);
}

void calctotal()
{
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            totalsum[map[row][col] - '1']++;
}

void apply(char c)
{
    char d = map[9][9];
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            if (reach[row][col] == d)
                map[row][col] = c;
}

int main()
{
    char c, best;
    size_t steps = 0;
    FILE *fp;

    if (!(fp = fopen("floodtest", "r")))
        return 1;

    while (loadmap(fp)) {
        do {
            memset(reach, 0, sizeof reach);
            memset(reachsum, 0, sizeof reachsum);
            calcreach(true, 9, 9);
            if (reachsum[map[9][9] - '1'] == 361)
                break;

            memset(totalsum, 0, sizeof totalsum);
            calctotal();

            reachsum[map[9][9] - '1'] = 0;
            for (best = 0, c = 0; c < 6; c++) {
                if (!reachsum[c])
                    continue;
                if (reachsum[c] == totalsum[c]) {
                    best = c;
                    break;
                } else if (reachsum[c] > reachsum[best]) {
                    best = c;
                }
            }

            apply(best + '1');
        } while (++steps);
    }

    fclose(fp);

    printf("steps: %zu\n", steps);
    return 0;
}

ทำได้ดีมากวิลเล็มขอบคุณที่พูดถึงฉันในรายละเอียดของคุณ ฉันได้รับเกียรติจากพระคุณของคุณ
Herjan

ไม่มีปัญหาเรียน Herjan
SteelTermite

โดยวิธีการที่คำสั่งของคุณ "มันคะแนนน้อยกว่า Herjan" ล้าสมัยไปแล้วฉันเพิ่งใช้การปรับปรุงที่ฉันพูดถึง (ในอีเมล);) ขอให้โชคดีชนะฉันตอนนี้!
Herjan

1
ข้างหน้าคุณ 515 ขั้นเคยได้ยินการเพิ่ม / ลบ '=' ในการเปรียบเทียบ heheh
Herjan

แท้จริงแล้ว Herjan ฉันจะอัปเดตการส่งตามคำแนะนำของคุณ
SteelTermite

3

Java - 2,245,529 2,201,995 ขั้นตอน

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

ปัจจุบันส่งออกไปยัง stdout จนกว่าฉันจะรู้รูปแบบการป้อนข้อมูลของผู้ตรวจสอบ

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class FloodPaint {

    private static final ForkJoinPool FORK_JOIN_POOL = new ForkJoinPool();

    public static void main(String[] arg) throws IOException, InterruptedException, ExecutionException {
        try (BufferedReader reader = new BufferedReader(new FileReader("floodtest"))) {
            int sum = 0;
            State initState = readNextInitState(reader);
            while (initState != null) {
                List<Integer> solution = generateSolution(initState);
                System.out.println(solution);
                sum += solution.size();
                initState = readNextInitState(reader);
            }
            System.out.println(sum);
        }
    }

    private static State readNextInitState(BufferedReader reader) throws IOException {
        int[] initGrid = new int[State.DIM * State.DIM];
        String line = reader.readLine();
        while ((line != null) && line.isEmpty()) {
            line = reader.readLine();
        }
        if (line == null) {
            return null;
        }
        for (int rowNo = 0; rowNo < State.DIM; ++rowNo) {
            for (int colNo = 0; colNo < State.DIM; ++colNo) {
                initGrid[(State.DIM * rowNo) + colNo] = line.charAt(colNo) - '0';
            }
            line = reader.readLine();
        }
        return new State(initGrid);
    }

    private static List<Integer> generateSolution(State initState) throws InterruptedException, ExecutionException {
        List<Integer> solution = new LinkedList<>();
        StateFactory stateFactory = new StateFactory();
        State state = initState;
        while (!state.isSolved()) {
            int num = findGoodNum(state, stateFactory);
            solution.add(num);
            state = state.getNextState(num, stateFactory);
        }
        return solution;
    }

    private static int findGoodNum(State state, StateFactory stateFactory) throws InterruptedException, ExecutionException {
        SolverTask task = new SolverTask(state, stateFactory);
        FORK_JOIN_POOL.invoke(task);
        return task.get();
    }

}

class SolverTask extends RecursiveTask<Integer> {

    private static final int DEPTH = 5;

    private final State state;
    private final StateFactory stateFactory;

    SolverTask(State state, StateFactory stateFactory) {
        this.state = state;
        this.stateFactory = stateFactory;
    }

    @Override
    protected Integer compute() {
        try {
            Map<Integer,AnalyzerTask> tasks = new HashMap<>();
            for (int num = 1; num <= 6; ++num) {
                if (num != state.getCenterNum()) {
                    State nextState = state.getNextState(num, stateFactory);
                    AnalyzerTask task = new AnalyzerTask(nextState, DEPTH - 1, stateFactory);
                    tasks.put(num, task);
                }
            }
            invokeAll(tasks.values());
            int bestValue = Integer.MAX_VALUE;
            int bestNum = -1;
            for (Map.Entry<Integer,AnalyzerTask> taskEntry : tasks.entrySet()) {
                int value = taskEntry.getValue().get();
                if (value < bestValue) {
                    bestValue = value;
                    bestNum = taskEntry.getKey();
                }
            }
            return bestNum;
        } catch (InterruptedException | ExecutionException ex) {
            throw new RuntimeException(ex);
        }
    }

}

class AnalyzerTask extends RecursiveTask<Integer> {

    private static final int DEPTH_THRESHOLD = 3;

    private final State state;
    private final int depth;
    private final StateFactory stateFactory;

    AnalyzerTask(State state, int depth, StateFactory stateFactory) {
        this.state = state;
        this.depth = depth;
        this.stateFactory = stateFactory;
    }

    @Override
    protected Integer compute() {
        return (depth < DEPTH_THRESHOLD) ? analyze() : split();
    }

    private int analyze() {
        return analyze(state, depth);
    }

    private int analyze(State state, int depth) {
        if (state.isSolved()) {
            return -depth;
        }
        if (depth == 0) {
            return state.getNumIslands();
        }
        int bestValue = Integer.MAX_VALUE;
        for (int num = 1; num <= 6; ++num) {
            if (num != state.getCenterNum()) {
                State nextState = state.getNextState(num, stateFactory);
                int nextValue = analyze(nextState, depth - 1);
                bestValue = Math.min(bestValue, nextValue);
            }
        }
        return bestValue;
    }

    private int split() {
        try {
            if (state.isSolved()) {
                return -depth;
            }
            Collection<AnalyzerTask> tasks = new ArrayList<>(5);
            for (int num = 1; num <= 6; ++num) {
                State nextState = state.getNextState(num, stateFactory);
                AnalyzerTask task = new AnalyzerTask(nextState, depth - 1, stateFactory);
                tasks.add(task);
            }
            invokeAll(tasks);
            int bestValue = Integer.MAX_VALUE;
            for (AnalyzerTask task : tasks) {
                int nextValue = task.get();
                bestValue = Math.min(bestValue, nextValue);
            }
            return bestValue;
        } catch (InterruptedException | ExecutionException ex) {
            throw new RuntimeException(ex);
        }
    }

}

class StateFactory {

    private static final int INIT_CAPACITY = 40000;
    private static final float LOAD_FACTOR = 0.9f;

    private final ReadWriteLock cacheLock = new ReentrantReadWriteLock();
    private final Map<List<Integer>,State> cache = new HashMap<>(INIT_CAPACITY, LOAD_FACTOR);

    State get(int[] grid) {
        List<Integer> stateKey = new IntList(grid);
        State state;
        cacheLock.readLock().lock();
        try {
            state = cache.get(stateKey);
        } finally {
            cacheLock.readLock().unlock();
        }
        if (state == null) {
            cacheLock.writeLock().lock();
            try {
                state = cache.get(stateKey);
                if (state == null) {
                    state = new State(grid);
                    cache.put(stateKey, state);
                }
            } finally {
                cacheLock.writeLock().unlock();
            }
        }
        return state;
    }

}

class State {

    static final int DIM = 19;
    private static final int CENTER_INDEX = ((DIM * DIM) - 1) / 2;

    private final int[] grid;
    private int numIslands;

    State(int[] grid) {
        this.grid = grid;
        numIslands = calcNumIslands(grid);
    }

    private static int calcNumIslands(int[] grid) {
        int numIslands = 0;
        BitSet uncounted = new BitSet(DIM * DIM);
        uncounted.set(0, DIM * DIM);
        int index = -1;
        while (!uncounted.isEmpty()) {
            index = uncounted.nextSetBit(index + 1);
            BitSet island = new BitSet(DIM * DIM);
            generateIsland(grid, index, grid[index], island);
            ++numIslands;
            uncounted.andNot(island);
        }
        return numIslands;
    }

    private static void generateIsland(int[] grid, int index, int num, BitSet island) {
        if ((grid[index] == num) && !island.get(index)) {
            island.set(index);
            if ((index % DIM) > 0) {
                generateIsland(grid, index - 1, num, island);
            }
            if ((index % DIM) < (DIM - 1)) {
                generateIsland(grid, index + 1, num, island);
            }
            if ((index / DIM) > 0) {
                generateIsland(grid, index - DIM, num, island);
            }
            if ((index / DIM) < (DIM - 1)) {
                generateIsland(grid, index + DIM, num, island);
            }
        }
    }

    int getCenterNum() {
        return grid[CENTER_INDEX];
    }

    boolean isSolved() {
        return numIslands == 1;
    }

    int getNumIslands() {
        return numIslands;
    }

    State getNextState(int num, StateFactory stateFactory) {
        int[] nextGrid = grid.clone();
        if (num != getCenterNum()) {
            flood(nextGrid, CENTER_INDEX, getCenterNum(), num);
        }
        State nextState = stateFactory.get(nextGrid);
        return nextState;
    }

    private static void flood(int[] grid, int index, int fromNum, int toNum) {
        if (grid[index] == fromNum) {
            grid[index] = toNum;
            if ((index % 19) > 0) {
                flood(grid, index - 1, fromNum, toNum);
            }
            if ((index % 19) < (DIM - 1)) {
                flood(grid, index + 1, fromNum, toNum);
            }
            if ((index / 19) > 0) {
                flood(grid, index - DIM, fromNum, toNum);
            }
            if ((index / 19) < (DIM - 1)) {
                flood(grid, index + DIM, fromNum, toNum);
            }
        }
    }

}

class IntList extends AbstractList<Integer> implements List<Integer> {

    private final int[] arr;
    private int hashCode = -1;

    IntList(int[] arr) {
        this.arr = arr;
    }

    @Override
    public int size() {
        return arr.length;
    }

    @Override
    public Integer get(int index) {
        return arr[index];
    }

    @Override
    public Integer set(int index, Integer value) {
        int oldValue = arr[index];
        arr[index] = value;
        return oldValue;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof IntList) {
            IntList arg = (IntList) obj;
            return Arrays.equals(arr, arg.arr);
        }
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        if (hashCode == -1) {
            hashCode = 1;
            for (int elem : arr) {
                hashCode = 31 * hashCode + elem;
            }
        }
        return hashCode;
    }

}

น่าประทับใจคุณสามารถทำให้มันเขียนขั้นตอนในไฟล์ได้หรือไม่? เพื่อให้เราสามารถตรวจสอบได้?
Herjan

@ Herjan ดูเหมือนว่ารหัสของเขาเป็นการตรวจสอบตนเอง ดู isSolved ()
BurntPizza

@BurntPizza งั้นเหรอ? รหัสของฉันยังตรวจสอบตัวเองได้ฮ่า ๆ ... ฉันหมายความว่ามันอาจผิดเหมือนรหัสของฉันเอง
Herjan

isSolved () ไม่ใช่สำหรับการตรวจสอบ แต่เป็นการยกเลิก สำหรับการเขียน - จะทำในเวอร์ชันถัดไป
MrBackend

ฉันสนใจถ้าฮิวริสติกที่ทำให้มันค้นหา 5 ขั้นตอนลึกกว่าหากจำนวนขั้นตอนที่พบสำหรับ 4 นั้นมากกว่า24จะส่งผลให้รันไทม์มีประสิทธิภาพมากขึ้น
Joe Z.

2

รายการสุดท้ายของฉัน: C - 2,384,020 ขั้นตอน

ครั้งนี้เป็น 'การตรวจสอบความเป็นไปได้ทั้งหมด' ... คะแนนนี้จะได้รับจากการตั้งค่าความลึกที่ 3 ความลึกที่ 5 ควรให้ถึงขั้นตอน ~ 2.1M ... ช้าเกินไป ความลึก 20+ ให้ขั้นตอนน้อยที่สุดเท่าที่ทำได้ (แค่ตรวจสอบการแข่งขันทั้งหมดและชนะการแข่งขันสั้นที่สุด) ... มันมีขั้นตอนน้อยที่สุดแม้ว่าฉันจะเกลียดเพราะมันดีขึ้นเล็กน้อย แต่ประสิทธิภาพแย่ลง ฉันชอบรายการ C อื่น ๆ ของฉันซึ่งอยู่ในโพสต์นี้ด้วย

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

char map[19][19], reach[19][19];
int reachsum[6], totalsum[6], mapCount = 0;
FILE *stepfile;

bool loadmap(FILE *fp)
{
    fprintf(stepfile, "%s", "\n");

    mapCount++;

    char buf[19 + 2];
    size_t row = 0;

    while (fgets(buf, sizeof buf, fp) && row < 19) {
        if (strlen(buf) != 20)
            break;
        memcpy(map[row++], buf, 19);
    }
    return row == 19;
}

void calcreach(bool first, size_t row, size_t col);
void check(char c, bool first, size_t row, size_t col)
{
    if (map[row][col] == c)
        calcreach(first, row, col);
    else if (first)
        calcreach(false, row, col);
}

void calcreach(bool first, size_t row, size_t col)
{
    char c = map[row][col];

    reach[row][col] = c;
    reachsum[c - '1']++;
    if (row < 18 && !reach[row + 1][col])
        check(c, first, row + 1, col);
    if (col < 18 && !reach[row][col + 1])
        check(c, first, row, col + 1);
    if (row > 0 && !reach[row - 1][col])
        check(c, first, row - 1, col);
    if (col > 0 && !reach[row][col - 1])
        check(c, first, row, col - 1);
}

void calctotal()
{
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            totalsum[map[row][col] - '1']++;
}

void apply(char c)
{
    char d = map[9][9];
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            if (reach[row][col] == d)
                map[row][col] = c;
}

int pown(int x, int y){
    int p = 1;
    for(int i = 0; i < y; i++){
        p = p * x;
    }

    return p;
}

int main()
{
    size_t steps = 0;
    FILE *fp;

    if (!(fp = fopen("floodtest", "r")))
        return 1;
    if(!(stepfile = fopen("steps.txt", "w")))
        return 1;

    const int depth = 5;
    char possibilities[pown(6, depth)][depth];
    int t = 0;
    for(int a = 0; a < 6; a++){
        for(int b = 0; b < 6; b++){
            for(int c = 0; c < 6; c++){
                for(int d = 0; d < 6; d++){
                    for(int e = 0; e < 6; e++){
                        possibilities[t][0] = (char)(a + '1');
                        possibilities[t][1] = (char)(b + '1');
                        possibilities[t][2] = (char)(c + '1');
                        possibilities[t][3] = (char)(d + '1');
                        possibilities[t++][4] = (char)(e + '1');
                    }
                }
            }
        }
    }
    poes:
    while (loadmap(fp)) {
        do {
            char map2[19][19];
            memcpy(map2, map, sizeof(map));

            memset(reach, 0, sizeof reach);
            memset(reachsum, 0, sizeof reachsum);
            calcreach(true, 9, 9);

            int best = 0, index = 0, end = depth;
            for(int i = 0; i < pown(6, depth); i++){
                for(int d = 0; d < end; d++){

                    apply(possibilities[i][d]);

                    memset(reach, 0, sizeof reach);
                    memset(reachsum, 0, sizeof reachsum);
                    calcreach(true, 9, 9);

                    if(reachsum[map[9][9] - '1'] == 361 && d < end){
                        end = d+1;
                        index = i;
                        break;
                    }
                }
                if(end == depth && best < reachsum[map[9][9] - '1']){
                    best = reachsum[map[9][9] - '1'];
                    index = i;
                }

                memcpy(map, map2, sizeof(map2));
                memset(reach, 0, sizeof reach);
                memset(reachsum, 0, sizeof reachsum);
                calcreach(true, 9, 9);
            }

            for(int d = 0; d < end; d++){

                apply(possibilities[index][d]);

                memset(reach, 0, sizeof reach);
                memset(reachsum, 0, sizeof reachsum);
                calcreach(true, 9, 9);

                fprintf(stepfile, "%c", possibilities[index][d]);
                steps++;
            }
            if(reachsum[map[9][9] - '1'] == 361)
                goto poes;
        } while (1);
    }

    fclose(fp);
    fclose(stepfile);

    printf("steps: %zu\n", steps);
    return 0;
}

AI ที่ได้รับการปรับปรุงอีกฉบับหนึ่งเขียนใน C - 2,445,761 ขั้นตอน

ขึ้นอยู่กับ SteelTermite's:

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

char map[19][19], reach[19][19];
int reachsum[6], totalsum[6], mapCount = 0;
FILE *stepfile;

bool loadmap(FILE *fp)
{
    fprintf(stepfile, "%s", "\n");

    if(mapCount % 1000 == 0)
        printf("mapCount = %d\n", mapCount);

    mapCount++;

    char buf[19 + 2];
    size_t row = 0;

    while (fgets(buf, sizeof buf, fp) && row < 19) {
        if (strlen(buf) != 20)
            break;
        memcpy(map[row++], buf, 19);
    }
    return row == 19;
}

void calcreach(bool first, size_t row, size_t col);
void check(char c, bool first, size_t row, size_t col)
{
    if (map[row][col] == c)
        calcreach(first, row, col);
    else if (first)
        calcreach(false, row, col);
}

void calcreach(bool first, size_t row, size_t col)
{
    char c = map[row][col];

    reach[row][col] = c;
    reachsum[c - '1']++;
    if (row < 18 && !reach[row + 1][col])
        check(c, first, row + 1, col);
    if (col < 18 && !reach[row][col + 1])
        check(c, first, row, col + 1);
    if (row > 0 && !reach[row - 1][col])
        check(c, first, row - 1, col);
    if (col > 0 && !reach[row][col - 1])
        check(c, first, row, col - 1);
}

void calctotal()
{
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            totalsum[map[row][col] - '1']++;
}

void apply(char c)
{
    char d = map[9][9];
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            if (reach[row][col] == d)
                map[row][col] = c;
}

int main()
{
    char c, best, answer;
    size_t steps = 0;
    FILE *fp;

    if (!(fp = fopen("floodtest", "r")))
        return 1;
    if(!(stepfile = fopen("steps.txt", "w")))
            return 1;

    while (loadmap(fp)) {
        do {
            memset(reach, 0, sizeof reach);
            memset(reachsum, 0, sizeof reachsum);
            calcreach(true, 9, 9);
            if (reachsum[map[9][9] - '1'] == 361)
                break;

            memset(totalsum, 0, sizeof totalsum);
            calctotal();

            reachsum[map[9][9] - '1'] = 0;
            for (best = 0, c = 0; c < 6; c++) {
                if (!reachsum[c])
                    continue;
                if (reachsum[c] == totalsum[c]) {
                    best = c;
                    goto outLoop;
                } else if (reachsum[c] > reachsum[best]) {
                    best = c;
                }
            }

            char map2[19][19];
            memcpy(map2, map, sizeof(map));

            int temp = best;
            for(c = 0; c < 6; c++){

                if(c != best){

                    apply(c + '1');

                    memset(reach, 0, sizeof reach);
                    memset(reachsum, 0, sizeof reachsum);
                    calcreach(true, 9, 9);
                    if (reachsum[best] == totalsum[best]) {

                        memcpy(map, map2, sizeof(map2));
                        memset(reach, 0, sizeof reach);
                        memset(reachsum, 0, sizeof reachsum);
                        calcreach(true, 9, 9);

                        if(temp == -1)
                            temp = c;
                        else if(reachsum[c] > reachsum[temp])
                            temp = c;
                    }

                    memcpy(map, map2, sizeof(map2));
                    memset(reach, 0, sizeof reach);
                    memset(reachsum, 0, sizeof reachsum);
                    calcreach(true, 9, 9);
                }
            }

outLoop:    answer = (char)(temp + '1');
            fprintf(stepfile, "%c", answer);
            apply(answer);
        } while (++steps);
    }

    fclose(fp);
    fclose(stepfile);

    printf("steps: %zu\n", steps);
    return 0;
}

... และ ~ 200K เพื่อเอาชนะของฉัน;)
MrBackend

คุณควรโพสต์แต่ละรายการเป็นคำตอบของแต่ละคน
Joe Z.

@JoeZ ขออภัย แต่มันให้ความรู้สึกเหมือนสแปมดังนั้นฉันจึงตัดสินใจรวมพวกมันไว้ในคำตอบเดียว (มันไม่สำคัญเลยว่านับว่าดีที่สุดเท่านั้น (นับว่าดีที่สุด = AI ที่มีจำนวนก้าวน้อยที่สุด) อย่างน้อยนั่นคือสิ่งที่ฉันคิด
Herjan

1

Java - 2,610,797 4,780,841 ขั้นตอน

(แก้ไขข้อผิดพลาดตอนนี้คะแนนแย่ลงอย่างมาก -_-)

นี่คือการส่งอัลกอริทึมอ้างอิงขั้นพื้นฐานของฉันเพียงแค่สร้างฮิสโตแกรมของกำลังสองบนขอบของพื้นที่ที่ทาสีและทาสีด้วยสีที่พบมากที่สุด วิ่ง 100k ในไม่กี่นาที

เห็นได้ชัดว่าจะไม่ชนะ แต่ก็ไม่แน่นอน ฉันอาจจะส่งอีกครั้งเพื่อสิ่งที่ฉลาด รู้สึกอิสระที่จะใช้อัลกอริทึมนี้เป็นจุดเริ่มต้น

ยกเลิกความคิดเห็นบรรทัดที่ถูกคอมเม้นต์สำหรับเอาต์พุตเต็ม ค่าเริ่มต้นของการพิมพ์ # ของขั้นตอนที่ดำเนินการ

import java.io.*;
import java.util.*;

public class PainterAI {

    public static void main(String[] args) throws IOException {

        int totalSteps = 0, numSolved = 0;

        char[] board = new char[361];
        Scanner s = new Scanner(new File("floodtest"));
        long startTime = System.nanoTime();
        caseloop: while (s.hasNextLine()) {
            for (int l = 0; l < 19; l++) {
                String line = s.nextLine();
                if (line.isEmpty())
                    continue caseloop;
                System.arraycopy(line.toCharArray(), 0, board, l * 19, 19);
            }

            List<Character> colorsUsed = new ArrayList<>();
            Stack<Integer> nodes = new Stack<>();

            for (;; totalSteps++) {
                char p = board[180];
                int[] occurrences = new int[7];
                nodes.add(180);
                int numToPaint = 0;
                while (!nodes.empty()) {
                    int n = nodes.pop();
                    if (n < 0 || n > 360)
                        continue;
                    if (board[n] == p) {
                        board[n] = 48;
                        numToPaint++;
                        if (n % 19 > 0)
                            nodes.push(n - 1);
                        if(n%19<18)
                            nodes.push(n + 1);
                        if(n/19>0)
                            nodes.push(n - 19);
                        if(n/19<18)
                            nodes.push(n + 19);
                    } else
                        occurrences[board[n] - 48]++;
                }
                if (numToPaint == 361)
                    break;
                char mostFrequent = 0;
                int times = -1;
                for (int i = 1; i < 7; i++)
                    if (occurrences[i] > times) {
                        times = occurrences[i];
                        mostFrequent = (char) (i + 48);
                    }
                for (int i = 0; i < 361; i++)
                    if (board[i] == 48)
                        board[i] = mostFrequent;
                //colorsUsed.add(mostFrequent);
            }
            numSolved++;

            /*String out = "";
            for (Character c : colorsUsed)
                out += c;
            System.out.println(out); //print output*/
        }
        s.close();
        System.out.println("Total steps to solve all cases: " + totalSteps);
        System.out.printf("\nSolved %d test cases in %.2f seconds", numSolved, (System.nanoTime() - startTime) / 1000000000.);
    }
}

เล่นกอล์ฟถึง 860 ตัวอักษร (ไม่รวมบรรทัดใหม่สำหรับการจัดรูปแบบ) แต่อาจหดได้มากกว่านี้ถ้าฉันรู้สึกอยากลอง:

import java.io.*;import java.util.*;class P{
public static void main(String[]a)throws Exception{int t=0;char[]b=new char[361];
Scanner s=new Scanner(new File("floodtest"));c:while(s.hasNextLine()){
for(int l=0;l<19;l++){String L=s.nextLine();if(L.isEmpty())continue c;
System.arraycopy(L.toCharArray(),0,b,l*19,19);}List<Character>u=new ArrayList<>();
Stack<Integer>q=new Stack<>();for(int[]o=new int[7];;t++){char p=b[180];q.add(180);
int m=0;while(!q.empty()){int n=q.pop();if(n<0|n>360)continue;if(b[n]==p){b[n]=48;m++;
if(n%19>0)q.add(n-1);if(n%19<18)q.add(n+1);if(n/19>0)q.add(n-19);if(n/19<18)
q.add(n+19);}else o[b[n]-48]++;}if(m==361)break;
char f=0;int h=0;for(int i=1;i<7;i++)if(o[i]>h){h=o[i];f=(char)(i+48);}
for(int i=0;i<361;i++)if(b[i]==48)b[i]=f;u.add(f);}String y="";for(char c:u)y+=c;
System.out.println(y);}s.close();System.out.println("Steps: "+t);}}

เหตุผลเดียวที่มัน "ไม่แน่นอน" เป็นเพราะโซลูชันอ้างอิงของฉันอยู่ที่นั่นเพื่อกำจัดสิ่งต่างๆ จริงๆแล้วมันเป็นสถานที่สุดท้ายจากการส่งทั้งหมดโดยคนอื่น ๆ ในขณะนี้: P
Joe Z.

@JoeZ มันอยู่ตรงหน้าของ SteelTermite แต่เขาก็พัฒนาให้ดีขึ้น ฉันหมายถึงนี่เป็น "ขั้นตอนตรรกะถัดไปจากไร้เดียงสา" ฉันจะเป็นห่วงถ้ามันทำได้ดี P
BurntPizza

1

Haskell - 2,475,056 ขั้นตอน

อัลกอริทึมคล้ายกับที่แนะนำโดย MrBackend ในความคิดเห็น ข้อแตกต่างคือ: ข้อเสนอแนะของเขาพบเส้นทางที่ถูกที่สุดไปยังจัตุรัสราคาสูงสุดเหมืองอย่างตะกละตะกลามลดความเยื้องศูนย์ของกราฟในทุกขั้นตอน

import Data.Array
import qualified Data.Map as M
import Data.Word
import Data.List
import Data.Maybe
import Data.Function (on)
import Data.Monoid
import Control.Arrow
import Control.Monad (liftM)
import System.IO
import System.Environment
import Control.Parallel.Strategies
import Control.DeepSeq

type Grid v = Array (Word8,Word8) v

main = do
  (ifn:_) <- getArgs
  hr <- openFile ifn ReadMode
  sp <- liftM parseFile $ hGetContents hr
  let (len,sol) = turns (map solve sp `using` parBuffer 3 (evalList rseq))
  putStrLn $ intercalate "\n" $ map (concatMap show) sol
  putStrLn $ "\n\nTotal turns: " ++ (show len)

turns :: [[a]] -> (Integer,[[a]])
turns l = rl' 0 l where
  rl' c [] = (c,[])
  rl' c (k:r) = let
   s = c + genericLength k
   (s',l') = s `seq` rl' s r
   in (s',k:l')

centrepoint :: Grid v -> (Word8,Word8)
centrepoint g = let
  ((x0,y0),(x1,y1)) = bounds g
  med l h = let t = l + h in t `div` 2 + t `mod` 2
  in (med x0 x1, med y0 y1)

neighbours :: Grid v -> (Word8,Word8) -> [(Word8,Word8)]
neighbours g (x,y) = filter
  (inRange $ bounds g)
  [(x,y+1),(x+1,y),(x,y-1),(x-1,y)]

areas :: Eq v => Grid v -> [[(Word8,Word8)]]
areas g = p $ indices g where
  p [] = []
  p (a:r) = f : p (r \\ f) where
    f = s g [a] []
s g [] _ = []
s g (h:o) v = let
  n = filter (((==) `on` (g !)) h) $ neighbours g h
  in h : s g ((n \\ (o ++ v)) ++ o) (h : v)

applyFill :: Eq v => v -> Grid v -> Grid v
applyFill c g = g // (zip fa $ repeat c) where
  fa = s g [centrepoint g] []

solve g = solve' gr' where
  aa = areas g
  cp = centrepoint g
  ca = head $ head $ filter (elem cp) aa
  gr' = M.fromList $ map (
    \r1 -> (head r1, map head $ filter (
      \r2 -> head r1 /= head r2 &&
        (not $ null $ intersect (concatMap (neighbours g) r1) r2)
     ) aa
    )
   ) aa
  solve' gr
    | null $ tail $ M.keys $ gr = []
    | otherwise = best : solve' ngr where
      djk _ [] = []
      djk v ((n,q):o) = (n,q) : djk (q:v) (
        o ++ zip (repeat (n+1))
        ((gr M.! q) \\ (v ++ map snd o))
       )
      dout = djk [] [(0,ca)]
      din = let
        m = maximum $ map fst dout
        s = filter ((== m) . fst) dout
        in djk [] s
      rc = filter (flip elem (gr M.! ca) . snd) din
      frc = let
        m = minimum $ map fst rc
        in map snd $ filter ((==m) . fst) rc
      msq = concat $ filter (flip elem frc . head) aa
      clr = map (length &&& head) $ group $ sort $ map (g !) msq
      best = snd $ maximumBy (compare `on` fst) clr
      ngr = let
        ssm = filter ((== best) . (g !)) $ map snd rc
        sml = (concatMap (gr M.!) ssm)
        ncl = ((gr M.! ca) ++ sml) \\ (ca : ssm)
        brk = M.insert ca ncl $ M.filterWithKey (\k _ ->
          (not . flip elem ssm) k
         ) gr
        in M.map 
          (\l -> nub $ map (\e -> if e `elem` ssm then ca else e) l)
          brk


parseFile :: String -> [Grid Word8]
parseFile f = map mk $ filter (not . null . head) $ groupBy ((==) `on` null) $
  map (map ((read :: String -> Word8) . (:[]))) $ lines f where
    mk :: [[Word8]] -> Grid Word8
    mk m = let
      w = fromIntegral (length $ head m) - 1
      h = fromIntegral (length m) - 1
      in array ((0,0),(w,h)) [ ((x,y),v) |
        (y,l) <- zip [h,h-1..] m,
        (x,v) <- zip [0..] l
       ]

showGrid :: Grid Word8 -> String
showGrid g = intercalate "\n" l where
  l = map sl $ groupBy ((==) `on` snd) $
    sortBy ((flip (compare `on` snd)) <> (compare `on` fst)) $
    indices g
  sl = intercalate " " . map (show . (g !))

testsolve = do
  hr <- openFile "floodtest" ReadMode
  sp <- liftM (head . parseFile) $ hGetContents hr
  let
   sol = solve sp
   a = snd $ mapAccumL (\g s -> let g' = applyFill s g in (g',g')) sp sol
  sequence_ $ map (\g -> putStrLn (showGrid g) >> putStrLn "\n") a

ทำงานเสร็จหรือยัง
Joe Z.

ยังไม่เสร็จมันอาจจะเสร็จแล้วถ้าฉันปล่อยให้มันทำงานข้ามคืน แต่พัดลมก็มีเสียงดังดังนั้นฉันจึงจำศีลในคอมพิวเตอร์ มันทำงานอีกครั้งตอนนี้จะตรวจสอบอีกครั้งเมื่อฉันกลับถึงบ้านจากที่ทำงาน
Jeremy List

มันล้มเหลวเนื่องจากการล้นสแต็คการแก้ไขในขณะนี้เพื่อหลีกเลี่ยง
รายการเจเรมี

1

C # - 2,383,569

มันเป็นการสำรวจเชิงลึกของวิธีแก้ปัญหาที่เป็นไปได้ซึ่งเลือกเส้นทางการปรับปรุงที่ดีที่สุด (คล้าย / เหมือนกับรายการ C ของ Herjan) ยกเว้นฉันจะกลับคำสั่งการสร้างโซลูชันผู้สมัครอย่างชาญฉลาดหลังจากเห็น Herjan โพสต์ตัวเลขเดียวกัน ใช้เวลามากกว่า 12 ชั่วโมงในการวิ่ง

class Solver
{
    static void Main()
    {
        int depth = 3;
        string text = File.ReadAllText(@"C:\TEMP\floodtest.txt");
        text = text.Replace("\n\n", ".").Replace("\n", "");
        int count = 0;
        string[] tests = text.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
        for (int i = 0; i < tests.Length; i++)
        {
            Solver s = new Solver(tests[i]);
            string k1 = s.solve(depth);
            count += k1.Length;
            Console.WriteLine(((100 * i) / tests.Length) + " " + i + " " + k1.Length + " " + count + " " + k1);
        }
        Console.WriteLine(count);
    }

    public readonly int MAX_DIM;
    public char[] board;
    public Solver(string prob)
    {
        board = read(prob);
        MAX_DIM = (int)Math.Sqrt(board.Length);
    }

    public string solve(int d)
    {
        var sol = "";
        while (score(eval(copy(board), sol)) != board.Length)
        {
            char[] b = copy(board);
            eval(b, sol);

            var canidates = new List<string>();
            buildCanidates("", canidates, d);
            var best = canidates.Select(c => new {score = score(eval(copy(b), c)), sol = c}).ToList().OrderByDescending(t=>t.score).ThenBy(v => v.sol.Length).First();
            sol = sol + best.sol[0];
        }
        return sol;
    }

    public void buildCanidates(string b, List<string> r, int d)
    {
        if(b.Length>0)
            r.Add(b);
        if (d > 0)
        {
            r.Add(b);
            for (char i = '6'; i >= '1'; i--)
                if(b.Length == 0 || b[b.Length-1] != i)
                    buildCanidates(b + i, r, d - 1);
        }
    }

    public char[] read(string s)
    {
        return s.Where(c => c >= '0' && c <= '9').ToArray();
    }

    public void print(char[] b)
    {
        for (int i = 0; i < MAX_DIM; i++)
        {
            for(int j=0; j<MAX_DIM; j++)
                Console.Write(b[i*MAX_DIM+j]);
            Console.WriteLine();
        }
        Console.WriteLine();
    }

    public char[] copy(char[] b)
    {
        char[] n = new char[b.Length];
        for (int i = 0; i < b.Length; i++)
            n[i] = b[i];
        return n;
    }

    public char[] eval(char[] b, string sol)
    {
        foreach (char c in sol)
            eval(b, c);
        return b;
    }

    public void eval(char[] b, char c)
    {
        foreach (var l in flood(b))
            b[l] = c;
    }

    public int score(char[] b)
    {
        return flood(b).Count;
    }

    public List<int> flood(char[] b)
    {
        int start = (MAX_DIM * (MAX_DIM / 2)) + (MAX_DIM / 2);
        var check = new List<int>(MAX_DIM * MAX_DIM);
        bool[] seen = new bool[b.Length];
        var hits = new List<int>(MAX_DIM*MAX_DIM);

        check.Add(start);
        seen[start]=true;
        char target = b[start];

        int at = 0;
        while (at<check.Count)
        {
            int toCheck = check[at++];
            if (b[toCheck] == target)
            {
                addNeighbors(check, seen, toCheck);
                hits.Add(toCheck);
            }
        }
        return hits;
    }

    public void addNeighbors(List<int> check, bool[] seen, int loc)
    {
        int x = loc / MAX_DIM;
        int y = loc % MAX_DIM;
        addNeighbor(check, seen, x, y - 1);
        addNeighbor(check, seen, x, y + 1);
        addNeighbor(check, seen, x - 1, y);
        addNeighbor(check, seen, x + 1, y);
    }

    public void addNeighbor(List<int> check, bool[] seen, int x, int y)
    {
        if (x >= 0 && x < MAX_DIM && y >= 0 && y < MAX_DIM)
        {
            int l = (x * MAX_DIM) + y;
            if (!seen[l])
            {
                seen[l] = true;
                check.Add(l);
            }
        }
    }
}

1

Java - 2,403,189

BUILD SUCCESSFUL (total time: 220 minutes 15 seconds)

นี่ควรจะเป็นความพยายามของฉันที่กำลังดุร้าย แต่! การใช้งานตัวเลือก "ดีที่สุด" เชิงลึกครั้งแรกของฉันได้รับ:

2,589,328 - BUILD SUCCESSFUL (total time: 3 minutes 11 seconds)

รหัสที่ใช้สำหรับทั้งคู่นั้นเหมือนกันกับแรงเดรัจฉานที่เก็บ "ภาพรวม" ของการเคลื่อนไหวที่เป็นไปได้อื่น ๆ และเรียกใช้อัลกอริทึมเหนือสิ่งอื่นใด


  • ประเด็น

หากทำงานด้วยความล้มเหลวแบบสุ่มของวิธีการส่งผ่านจะเกิดขึ้น ฉันตั้งค่ารายการปริศนา 100 รายการแรกในการทดสอบหน่วยและสามารถผ่าน 100% ได้ แต่ไม่ถึง 100% ของเวลา เพื่อชดเชยฉันเพิ่งติดตามหมายเลขตัวต่อปัจจุบันที่เวลาที่ล้มเหลวและเริ่มเธรดใหม่เพื่อเลือกจุดที่ตัวสุดท้ายค้างไว้ แต่ละเธรดเขียนผลลัพธ์ตามลำดับลงในไฟล์ พูลไฟล์ถูกย่อให้เป็นไฟล์เดียว

  • เข้าใกล้

Nodeแสดงถึงไพ่ / ตารางของกระดานและเก็บการอ้างอิงถึงเพื่อนบ้านทั้งหมด ติดตามสามตัวแปร:Set<Node> , , การวนซ้ำแต่ละครั้งจะดูที่การจัดกลุ่มโหนดทั้งหมดตามค่าโดยเลือกจำนวนของโหนด "ที่ได้รับผลกระทบ" โหนดที่ได้รับผลกระทบเหล่านี้จะกลายเป็นเป้าหมายสำหรับการทำซ้ำครั้งถัดไปRemainingPaintedTargetsTargetscandidatetarget value

แหล่งที่มานั้นกระจายไปทั่วหลายคลาสและตัวอย่างข้อมูลนั้นไม่ได้มีความหมายมากนักจากบริบททั้งหมด แหล่งที่มาของฉันสามารถเรียกดูผ่านทางGitHub ฉันยังยุ่งกับการสาธิตJSFiddleสำหรับการสร้างภาพ

อย่างไรก็ตามวิธีการทำงานหนักของฉันจากSolver.java:

public void flood() {

 final Data data = new Data();
 consolidateCandidates(data, targets);

 input.add(data.getTarget());

 if(input.size() > SolutionRepository.getInstance().getThreshold()){
  //System.out.println("Exceeded threshold: " + input.toString());
  cancelled = true;
 }
 paintable.addAll(data.targets());
 remaining.removeAll(data.targets());

 if(!data.targets().isEmpty()){
  targets = data.potentialTargets(data.targets(), paintable);

  data.setPaintable(paintable);
  data.setRemaining(remaining);
  data.setInput(input);

  SolutionRepository.getInstance().addSnapshot(data, input);
 }
}

1

C # - 2,196,462 2,155,834

นี่เป็นวิธีเดียวกับ 'มองหาลูกหลานที่ดีที่สุด' อย่างมีประสิทธิภาพเหมือนกับตัวแก้ปัญหาอื่นของฉัน แต่ด้วยการเพิ่มประสิทธิภาพเพียงไม่กี่อย่างที่เพิ่งจะมาพร้อมกับความขนานทำให้มันไปถึงความลึก 5 ในเวลาน้อยกว่า 10 ชั่วโมง ในระหว่างการทดสอบฉันพบข้อผิดพลาดในต้นฉบับเช่นอัลกอริทึมอาจใช้เส้นทางที่ไม่มีประสิทธิภาพไปยังสถานะสิ้นสุด (บางครั้งก็ไม่ได้อธิบายถึงความลึกของรัฐด้วยคะแนน = 64 ค้นพบในขณะที่เล่นกับผลลัพธ์ของความลึก = 7)

ความแตกต่างที่สำคัญระหว่างสิ่งนี้กับตัวแก้ปัญหาก่อนหน้าคือมันทำให้สถานะเกมของเกม Flood อยู่ในความทรงจำดังนั้นจึงไม่จำเป็นต้องสร้าง 6 ^ 5 ใหม่ จากการใช้ CPU ในระหว่างการทำงานฉันค่อนข้างแน่ใจว่าสิ่งนี้ได้ย้ายจาก CPU ที่ถูกผูกไว้กับแบนด์วิดธ์หน่วยความจำ สนุกดี. บาปมากมาย

แก้ไข: เนื่องจากเหตุผลอัลกอริธึมความลึก 5 ไม่ได้ให้ผลลัพธ์ที่ดีที่สุดเสมอไป เพื่อปรับปรุงประสิทธิภาพลองทำความลึก 5 ... และ 4 ... และ 3 และ 2 และ 1 และดูว่าอะไรดีที่สุด โกนการเคลื่อนไหวอีก 40 พันครั้งแล้วหรือยัง เนื่องจากความลึก 5 เป็นจำนวนมากการเพิ่ม 4 ถึง 1 เท่านั้นจึงเพิ่มเวลาใช้งานจาก ~ 10hrs ถึง ~ 11hrs เย้!

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Collections.Generic;

public class Program
{
    static void Main()
    {
        int depth = 5;
        string text = File.ReadAllText(@"C:\TEMP\floodtest.txt");
        text = text.Replace("\n\n", ".").Replace("\n", "");
        int count = 0;
        string[] tests = text.Split(new [] { '.' }, StringSplitOptions.RemoveEmptyEntries);

        Stopwatch start = Stopwatch.StartNew();

        const int parChunk = 16*16;
        for (int i = 0; i < tests.Length; i += parChunk)
        {
            //did not know that parallel select didn't respect order
            string[] sols = tests.Skip(i).Take(parChunk).AsParallel().Select((t, idx) => new { s = new Solver2(t).solve(depth), idx}).ToList().OrderBy(v=>v.idx).Select(v=>v.s).ToArray();
            for (int j = 0; j < sols.Length; j++)
            {
                string k1 = sols[j];
                count += k1.Length;
                int k = i + j;
                int estimate = (int)((count*(long)tests.Length)/(k+1));
                Console.WriteLine(k + "\t" + start.Elapsed.TotalMinutes.ToString("F2") + "\t" + count + "\t" + estimate + "\t" + k1.Length + "\t" + k1);
            }
        }
        Console.WriteLine(count);
    }
}

public class Solver2
{
    public readonly int MAX_DIM;
    public char[] board;
    public Solver2(string prob)
    {
        board = read(prob);
        MAX_DIM = (int)Math.Sqrt(board.Length);
    }

    public string solve(int d)
    {
        string best = null;
        for (int k = d; k >= 1; k--)
        {
            string c = subSolve(k);
            if (best == null || c.Length < best.Length)
                best = c;
        }
        return best;
    }

    public string subSolve(int d)
    {
        State current = new State(copy(board), '\0', flood(board));
        var sol = "";

        while (current.score != board.Length)
        {
            State nextState = subSolve(current, d);
            sol = sol + nextState.key;
            current = nextState;
        }
        return sol;
    }

    public State subSolve(State baseState, int d)
    {
        if (d == 0)
            return baseState;
        if (!baseState.childrenGenerated)
        {
            for (int i = 0; i < baseState.children.Length; i++)
            {
                if (('1' + i) == baseState.key) continue; //no point in even eval'ing
                char[] board = copy(baseState.board);
                foreach(int idx in baseState.flood)
                    board[idx] = (char)('1' + i);
                List<int> f = flood(board);
                if (f.Count != baseState.score)
                    baseState.children[i] = new State(board, (char)('1' + i), f);
            }
            baseState.childrenGenerated = true;
        }
        State bestState = null;

        for (int i = 0; i < baseState.children.Length; i++)
            if (baseState.children[i] != null)
            {
                State bestChild = subSolve(baseState.children[i], d - 1);
                baseState.children[i].bestChildScore = bestChild.bestChildScore;
                if (bestState == null || bestState.bestChildScore < bestChild.bestChildScore)
                    bestState = baseState.children[i];
            }
        if (bestState == null || bestState.bestChildScore == baseState.score)
        {
            if (baseState.score == baseState.board.Length)
                baseState.bestChildScore = baseState.score*(d + 1);
            return baseState;
        }
        return bestState;
    }

    public char[] read(string s)
    {
        return s.Where(c => c >= '1' && c <= '6').ToArray();
    }

    public char[] copy(char[] b)
    {
        char[] n = new char[b.Length];
        for (int i = 0; i < b.Length; i++)
            n[i] = b[i];
        return n;
    }

    public List<int> flood(char[] b)
    {
        int start = (MAX_DIM * (MAX_DIM / 2)) + (MAX_DIM / 2);
        var check = new List<int>(MAX_DIM * MAX_DIM);
        bool[] seen = new bool[b.Length];
        var hits = new List<int>(MAX_DIM * MAX_DIM);

        check.Add(start);
        seen[start] = true;
        char target = b[start];

        int at = 0;
        while (at < check.Count)
        {
            int toCheck = check[at++];
            if (b[toCheck] == target)
            {
                addNeighbors(check, seen, toCheck);
                hits.Add(toCheck);
            }
        }
        return hits;
    }

    public void addNeighbors(List<int> check, bool[] seen, int loc)
    {
        //int x = loc / MAX_DIM;
        int y = loc % MAX_DIM;

        if(loc+MAX_DIM < seen.Length)
            addNeighbor(check, seen, loc+MAX_DIM);
        if(loc-MAX_DIM >= 0)
            addNeighbor(check, seen, loc-MAX_DIM);
        if(y<MAX_DIM-1)
            addNeighbor(check, seen, loc+1);
        if (y > 0)
            addNeighbor(check, seen, loc-1);
    }

    public void addNeighbor(List<int> check, bool[] seen, int l)
    {
        if (!seen[l])
        {
            seen[l] = true;
            check.Add(l);
        }
    }
}

public class State
{
    public readonly char[] board;
    public readonly char key;
    public readonly State[] children = new State[6];
    public readonly List<int> flood; 
    public readonly int score;
    public bool childrenGenerated;
    public int bestChildScore;
    public State(char[] board, char k, List<int> flood)
    {
        this.board = board;
        key = k;
        this.flood = flood;
        score = flood.Count;
        bestChildScore = score;
    }
}

ฉันลองใช้รหัสของคุณแล้วและไม่ได้รวบรวม มีข้อผิดพลาดใกล้การโทรวิธีแก้หนึ่งวิธี นอกจากนั้นยังมีคำสั่ง "ใช้" ที่หายไปสองสามรายการ อย่างไรก็ตามหากโปรแกรมของคุณแก้ปัญหาทุกอย่างในขั้นตอน 2.1M ขอแสดงความยินดีมันค่อนข้างน่าประทับใจ
tigrou

@tigrou ฉันไม่ได้มีปัญหากับการใช้คำสั่ง; แก้ไขการแก้ไขข้อผิดพลาดในการโทร - มันเป็นสิ่งประดิษฐ์จากการพยายามที่จะเพียงแค่อัปเดตรหัสแทนอีกครั้ง (คัดลอก / วาง) - มัน ขอโทษด้วยนะ
CoderTao

blarg คุณหมายถึงการใช้การนำเข้า namespace == แก้ไขที่เกินไป
CoderTao

ซีพียูตัวใดที่คุณใช้เพื่อแก้ปัญหาบอร์ดทุกระดับที่ความลึก 5 ใน 11 ชั่วโมง? ฉันใช้งานโปรแกรมภายใต้ I5 760@2.8Ghz ใช้เวลา 30 นาทีในการส่งออกแต่ละอัน 256 แผง จากนั้นจะใช้เวลา 8 วันในการแก้ปัญหากระดาน 100,000 อัน ซีพียูมีการสะท้อนระหว่างการใช้งาน 80-100% ในช่วงเวลานั้นทั้งสี่คอร์ใช้ อาจมีเครื่อง virtualbox ปัญหาที่ใช้ในการทดสอบ แต่นั่นช้ากว่าคุณประมาณ 16 เท่า (คุณบอกว่าใช้เวลา 11 ชั่วโมง)
tigrou

@tigrou ฉันกำลังใช้งาน i5 750@2.67 (ฮาร์ดแวร์อายุ 3-4 ปี) ภายใต้ VS โหมด Debug vs Release แตกต่างกัน 50% แต่ฉันสงสัยว่าจะอธิบายความแตกต่าง 16x หากคุณทำงานภายใต้โฮสต์ linux คุณอาจลองรวบรวมด้วยโมโน
CoderTao

1

Delphi XE3 2,979,145 ขั้นตอน

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

ทำงานปริศนาทั้งหมดใน 3 ชั่วโมงและ 6 นาที

program Main;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils,
  Classes,
  Generics.Collections,
  math,
  stopwatch in 'stopwatch.pas';

type
  myArr=array[0..1]of integer;
const
  MaxSize=19;
  puzLoc='here is my file';
var
  L:TList<TList<integer>>;
  puzzles:TStringList;
  sc:TList<myArr>;
  a:array[0..MaxSize-1,0..MaxSize-1] of Integer;
  aTest:array[0..MaxSize-1,0..MaxSize-1] of Integer;
  turns,midCol,sX,sY,i:integer;
  currBlob,biggestBlob,ColorBigBlob:integer;
  sTurn:string='';
  GLC:integer=0;

procedure FillArrays;
var
  ln,x,y:integer;
  puzzle:TStringList;
begin
  sc:=TList<myArr>.Create;
  puzzle:=TStringList.Create;    
  while puzzle.Count<19 do
  begin
    if puzzles[GLC]='' then
    begin
      inc(GLC);
      continue
    end
    else
      puzzle.Add(puzzles[GLC]);
    inc(GLC)
  end;    
  for y:=0to MaxSize-1do
    for x:=0to MaxSize-1do
      a[y][x]:=Ord(puzzle[y][x+1])-48;
  puzzle.Free;
end;
function CreateArr(nx,ny:integer):myArr;
begin
  Result[1]:=nx;
  Result[0]:=ny;
end;

procedure CreateBlob;
var
  tst:myArr;
  n,tx,ty:integer;
  currColor:integer;
begin
  n:=0;
  sc.Clear;
  currColor:=a[sy][sx];
  sc.Add(CreateArr(sx,sy));
  repeat
    tx:=sc[n][1];
    ty:=sc[n][0];
    if tx>0 then
      if a[ty][tx-1]=currColor then
      begin
        tst:=CreateArr(tx-1,ty);
        if not sc.Contains(tst)then
          sc.Add(tst);
      end;
    if tx<MaxSize-1 then
      if a[ty][tx+1]=currColor then
      begin
        tst:=CreateArr(tx+1,ty);
        if not sc.Contains(tst)then
          sc.Add(tst);
      end;
    if ty>0 then
      if a[ty-1][tx]=currColor then
      begin
        tst:=CreateArr(tx,ty-1);
        if not sc.Contains(tst)then
          sc.Add(tst);
      end;
    if ty<MaxSize-1 then
      if a[ty+1][tx]=currColor then
      begin
        tst:=CreateArr(tx,ty+1);
        if not sc.Contains(tst)then
          sc.Add(tst);
      end;
    inc(n);
  until (n=sc.Count);
end;

function BlobSize:integer;
var
  L:TList<myArr>;
  tst:myArr;
  n,currColor,tx,ty:integer;
begin
  n:=0;
  L:=TList<myArr>.Create;
  currColor:=aTest[sy][sx];
  L.Add(CreateArr(sx,sy));

  repeat
    tx:=L[n][1];
    ty:=L[n][0];
    if tx>0then
      if aTest[ty][tx-1]=currColor then
      begin
        tst:=CreateArr(tx-1,ty);
        if not L.Contains(tst)then
          L.Add(tst);
      end;
    if tx<MaxSize-1then
      if aTest[ty][tx+1]=currColor then
      begin
        tst:=CreateArr(tx+1,ty);
        if not L.Contains(tst)then
          L.Add(tst);
      end;
    if ty>0then
      if aTest[ty-1][tx]=currColor then
      begin
        tst:=CreateArr(tx,ty-1);
        if not L.Contains(tst)then
          L.Add(tst);
      end;
    if ty<MaxSize-1then
      if aTest[ty+1][tx]=currColor then
      begin
        tst:=CreateArr(tx,ty+1);
        if not L.Contains(tst)then
          L.Add(tst);
      end;
    inc(n);
  until n=l.Count;
  Result:=L.Count;
  L.Free;
end;

function AllsameColor(c:integer):boolean;
var
  cy,cx:integer;
begin
  Result:=true;
  for cy:=0to MaxSize-1do
    for cx:=0to MaxSize-1do
      if a[cy][cx]=c then
        continue
      else
        exit(false);
end;
procedure ChangeColors(old,new:integer; testing:boolean=false);
var
  i,j:integer;
  tst:myArr;
begin
  if testing then
  begin
    for i:= 0to MaxSize-1do
      for j:= 0to MaxSize-1do
        aTest[i][j]:=a[i][j];    
    for I:=0to sc.Count-1do
    begin
      tst:=sc[i];
      aTest[tst[0]][tst[1]]:=new;
    end;
  end
  else
  begin
    for I:=0to sc.Count-1do
    begin
      tst:=sc[i];
      a[tst[0]][tst[1]]:=new;
    end;
  end;
end;
var
  sw, swTot:TStopWatch;
  solved:integer=0;
  solutions:TStringList;
  steps:integer;
  st:TDateTime;
begin          
  st:=Now;
  writeln(FormatDateTime('hh:nn:ss',st));
  solutions:=TStringList.Create;
  puzzles:=TStringList.Create;
  puzzles.LoadFromFile(puzLoc);
  swTot:=TStopWatch.Create(true);
  turns:=0;
  repeat
    sTurn:='';    
    FillArrays;
    sX:=Round(Sqrt(MaxSize))+1;
    sY:=sX;    
    repeat
      biggestBlob:=0;
      ColorBigBlob:=0;
      midCol:=a[sy][sx];
      CreateBlob;
      for I:=1to 6do
      begin
        if I=midCol then continue;    
        ChangeColors(midCol,I,true);
        currBlob:=BlobSize;
        if currBlob>biggestBlob then
        begin
          biggestBlob:=currBlob;
          ColorBigBlob:=i;
        end;
      end;
      ChangeColors(midCol,ColorBigBlob);
      inc(turns);
      if sTurn='' then
        sTurn:=IntToStr(ColorBigBlob)
      else
        sTurn:=sTurn+', '+IntToStr(ColorBigBlob);
    until AllsameColor(a[sy][sx]);
    solutions.Add(sTurn);
    inc(solved);
    if solved mod 100=0then
      writeln(Format('Solved %d puzzles || %s',[solved,FormatDateTime('hh:nn:ss',Now-st)]));    
  until GLC>=puzzles.Count-1;    
  swTot.Stop;
  WriteLn(Format('solving these puzzles took %d',[swTot.Elapsed]));
  writeln(Format('Total moves: %d',[turns]));
  solutions.SaveToFile('save solutions here');
  readln;
end.

คิดเกี่ยวกับวิธีการ backtracing bruteforce ด้วย
อาจสนุกสำหรับสุดสัปดาห์นี้ ^^


0

Javascript / node.js - 2,588,847

Algoritm แตกต่างกันเล็กน้อยแล้วส่วนใหญ่ที่นี่เนื่องจากใช้ประโยชน์จากพื้นที่ที่คำนวณล่วงหน้าและสถานะที่แตกต่างระหว่างการคำนวณ มันทำงานน้อยกว่า 10 นาทีที่นี่ถ้าคุณกังวลเรื่องความเร็วเพราะจาวาสคริปต์

var fs = require('fs')


var file = fs.readFileSync('floodtest','utf8');
var boards = file.split('\n\n');
var linelength  = boards[0].split('\n')[0].length;
var maxdim = linelength* linelength;


var board = function(info){
    this.info =[];
    this.sameNeighbors = [];
    this.differentNeighbors = [];
    this.samedifferentNeighbors = [];
    for (var i = 0;i <info.length;i++ ){
        this.info.push(info[i]|0);
    };

    this.getSameAndDifferentNeighbors();
}

board.prototype.getSameAndDifferentNeighbors = function(){
    var self = this;
    var info = self.info;
    function getSameNeighbors(i,value,sameneighbors,diffneighbors){

        var neighbors = self.getNeighbors(i);
        for(var j =0,nl = neighbors.length; j< nl;j++){
            var index = neighbors[j];
            if (info[index]  === value ){
                if( sameneighbors.indexOf(index) === -1){
                    sameneighbors.push(index);
                    getSameNeighbors(index,value,sameneighbors,diffneighbors);
                }
            }else if( diffneighbors.indexOf(index) === -1){
                    diffneighbors.push(index);
            }
        } 

    }


    var sneighbors = [];
    var dneighbors = [];
    var sdneighbors = [];

    for(var i= 0,l= maxdim;i<l;i++){
        if (sneighbors[i] === undefined){
            var sameneighbors = [i];
            var diffneighbors = [];
            getSameNeighbors(i,info[i],sameneighbors,diffneighbors);
            for (var j = 0; j<sameneighbors.length;j++){
                var k = sameneighbors[j];
                sneighbors[k] = sameneighbors;
                dneighbors[k] = diffneighbors;
            } 
        }

    }

    for(var i= 0,l= maxdim;i<l;i++){
        if (sdneighbors[i] === undefined){
            var value = [];
            var dni = dneighbors[i];
            for (var j = 0,dnil = dni.length; j<dnil;j++){
                var dnij = dni[j];
                var sdnij = sneighbors[dnij];
                for(var k = 0,sdnijl = sdnij.length;k<sdnijl;k++){
                    if (value.indexOf(sdnij[k])=== -1){
                        value.push(sdnij[k]);
                    }
                }
            };
            var sni = sneighbors[i];
            for (var j=0,snil = sni.length;j<snil;j++){
                sdneighbors[sni[j]] = value;
            };
        };
    }
    this.sameNeighbors = sneighbors;
    this.differentNeighbors =  dneighbors;
    this.samedifferentNeighbors =sdneighbors;

}

board.prototype.getNeighbors = function(i){
        var returnValue = [];

        var index = i-linelength;
        if (index >= 0){
            returnValue.push(index);
        }

        index = i+linelength;
        if (index < maxdim){

            returnValue.push(index);
        }

        index = i-1;

        if (index >= 0 && index/linelength >>> 0 === i/linelength  >>> 0){
            returnValue.push(index);
        }
        index = i+1;
        if (index/linelength >>> 0 === i/linelength >>> 0){
            returnValue.push(index);
        }

        if (returnValue.indexOf(-1) !== -1){
            console.log(i,parseInt(index/linelength,10),parseInt(i/linelength,10));
        } 
        return returnValue 
}

board.prototype.solve = function(){
    var i,j;
    var info = this.info;
    var sameNeighbors = this.sameNeighbors;
    var samedifferentNeighbors = this.samedifferentNeighbors;
    var middle = 9*19+9;
    var maxValues = [];

    var done = {};
    for (i=0; i<sameNeighbors[middle].length;i++){
        done[sameNeighbors[middle][i]] = true;
    }
    var usefullNeighbors = [[],[],[],[],[],[],[]];
    var diff = [];
    var count = [0];

    count[1] = 0;
    count[2] = 0;
    count[3] = 0;
    count[4] = 0;
    count[5] = 0;
    count[6] = 0;

    var addusefullNeighbors = function(index,diff){

        var indexsamedifferentNeighbors =samedifferentNeighbors[index];
        for (var i=0;i < indexsamedifferentNeighbors.length;i++){
            var is = indexsamedifferentNeighbors[i];
            var value = info[is];
            if (done[is] === undefined && usefullNeighbors[value].indexOf(is) === -1){
                usefullNeighbors[value].push(is);
                diff.push(value);
            }

        }
    }
    addusefullNeighbors(middle,diff);


    while(  usefullNeighbors[1].length > 0 || usefullNeighbors[2].length > 0 ||
            usefullNeighbors[3].length > 0 || usefullNeighbors[4].length > 0 ||
            usefullNeighbors[5].length > 0 || usefullNeighbors[6].length > 0 ){
        for (i=0;i < diff.length;i++){ 
            count[diff[i]]++;
        };
        var maxValue = count.indexOf(Math.max.apply(null, count));
        diff.length = 0;
        var used = usefullNeighbors[maxValue];
        for (var i=0,ul = used.length;i < ul;i++){
            var index = used[i];
            if (info[index] === maxValue){
                done[index] = true;
                addusefullNeighbors(index,diff);
            }
        }
        used.length = 0;
        count[maxValue] = 0;


        maxValues.push(maxValue);
    }
    return maxValues.join("");
};
var solved = [];
var start = Date.now();
for (var boardindex =0;boardindex < boards.length;boardindex++){ 
    var b = boards[boardindex].replace(/\n/g,'').split('');
    var board2 = new board(b);
    solved.push(board2.solve());
};
var diff = Date.now()-start;
console.log(diff,boards.length);
console.log(solved.join('').length);
console.log("end");

fs.writeFileSync('solution.txt',solved.join('\n'),'utf8');

-3

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

การเติมน้ำท่วมนั้นไม่มีประสิทธิภาพอย่างยิ่งและต้องอาศัยการเรียกซ้ำ อาจจำเป็นต้องทำให้สแต็กของคุณใหญ่ขึ้นถ้ามันเล็กมาก ระบบบังคับเดรัจฉานใช้สายอักขระในการเก็บตัวเลขและการเพิ่มด้วยการพกพาเพื่อวนรอบตัวเลือกที่เป็นไปได้ทั้งหมด สิ่งนี้ยังไม่มีประสิทธิภาพอย่างมากเนื่องจากเป็นการทำซ้ำขั้นตอนส่วนใหญ่เป็นสองส่วนของเวลา

น่าเสียดายที่ฉันไม่สามารถทดสอบกับกรณีทดสอบทั้งหมดได้เนื่องจากฉันจะตายเมื่ออายุมากก่อนที่มันจะเสร็จสิ้น

#include <stdio.h>
#include <string.h>


#define GRID_SIZE       19

char grid[GRID_SIZE][GRID_SIZE] = { {3,3,5,4,1,3,4,1,5,3,3,5,4,1,3,4,1,5},
                                    {5,1,3,4,1,1,5,2,1,3,3,5,4,1,3,4,1,5},
                                    {6,5,2,3,4,3,3,4,3,3,3,5,4,1,3,4,1,5},
                                    {4,4,4,5,5,5,4,1,4,3,3,5,4,1,3,4,1,5},
                                    {6,2,5,3,3,1,1,6,6,3,3,5,4,1,3,4,1,5},
                                    {5,5,1,2,5,2,6,6,3,3,3,5,4,1,3,4,1,5},
                                    {6,1,1,5,3,6,2,3,6,3,3,5,4,1,3,4,1,5},
                                    {1,2,2,4,5,3,5,1,2,3,3,5,4,1,3,4,1,5},
                                    {3,6,6,1,5,1,3,2,4,3,3,5,4,1,3,4,1,5} };
char grid_save[GRID_SIZE][GRID_SIZE];

char test_grids[6][GRID_SIZE][GRID_SIZE];

void flood_fill(char x, char y, char old_colour, char new_colour)
{
    if (grid[y][x] == new_colour)
        return;

    grid[y][x] = new_colour;

    if (y > 0)
    {
        if (grid[y-1][x] == old_colour)
            flood_fill(x, y-1, old_colour, new_colour);
    }
    if (y < GRID_SIZE - 1)
    {
        if (grid[y+1][x] == old_colour)
            flood_fill(x, y+1, old_colour, new_colour);
    }

    if (x > 0)
    {
        if (grid[y][x-1] == old_colour)
            flood_fill(x-1, y, old_colour, new_colour);
    }
    if (x < GRID_SIZE - 1)
    {
        if (grid[y][x+1] == old_colour)
            flood_fill(x+1, y, old_colour, new_colour);
    }
}

bool check_grid(void)
{
    for (char i = 0; i < 6; i++)
    {
        if (!memcmp(grid, &test_grids[i][0][0], sizeof(grid)))
            return(true);
    }

    return(false);
}

void inc_string_num(char *s)
{
    char *c;

    c = s + strlen(s) - 1;
    *c += 1;

    // carry
    while (*c > '6')
    {
        *c = '1';
        if (c == s) // first char
        {
            strcat(s, "1");
            return;
        }
        c--;
        *c += 1;
    }
}

void print_grid(void)
{
    char x, y;
    for (y = 0; y < GRID_SIZE; y++)
    {
        for (x = 0; x < GRID_SIZE; x++)
            printf("%d ", grid[y][x]);
        printf("\n");
    }
    printf("\n");
}

int main(int argc, char* argv[])
{
    // create test grids for comparisons
    for (char i = 0; i < 6; i++)
        memset(&test_grids[i][0][0], i+1, GRID_SIZE*GRID_SIZE);

    char s[256] = "0";
    //char s[256] = "123456123456123455";
    memcpy(grid_save, grid, sizeof(grid));


    print_grid();
    do
    {
        memcpy(grid, grid_save, sizeof(grid));
        inc_string_num(s);

        for (unsigned int i = 0; i < strlen(s); i++)
        {
            flood_fill(4, 4, grid[4][4], s[i] - '0');
        }
    } while(!check_grid());
    print_grid();

    printf("%s\n", s);

    return 0;
}

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

โปรแกรมของคุณจะต้องกำหนดอย่างสมบูรณ์; อนุญาตให้ใช้โซลูชันปลอมเทียมได้ แต่โปรแกรมจะต้องสร้างเอาต์พุตเดียวกันสำหรับกรณีทดสอบเดียวกันทุกครั้ง

ตรวจสอบ

โปรแกรมที่ชนะจะใช้จำนวนขั้นตอนทั้งหมดน้อยที่สุดในการแก้กรณีทดสอบทั้งหมด 100,000 คดีที่พบในไฟล์นี้ (ไฟล์ข้อความซิปขนาด 14.23 MB) หากสองวิธีดำเนินการตามขั้นตอนจำนวนเท่ากัน (เช่นหากทั้งคู่พบกลยุทธ์ที่เหมาะสมที่สุด) โปรแกรมที่สั้นกว่าจะชนะ

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

#include <stdio.h>
#include <string.h>
#define A 9
int g[A][A]={{3,3,5,4,1,3,4,1,5},{5,1,3,4,1,1,5,2,1},{6,5,2,3,4,3,3,4,3},{4,4,4,5,5,5,4,1,4},{6,2,5,3,3,1,1,6,6},{5,5,1,2,5,2,6,6,3},{6,1,1,5,3,6,2,3,6},{1,2,2,4,5,3,5,1,2},{3,6,6,1,5,1,3,2,4}};
int s[A][A];
int t[6][A][A];
void ff(int x,int y,int o,int n)
{if (g[y][x]==n)return;g[y][x]=n;if (y>0){if(g[y-1][x]==o)ff(x,y-1,o,n);}if(y<A-1){if(g[y+1][x]==o)ff(x,y+1,o,n);}if(x>0){if (g[y][x-1] == o)ff(x-1,y,o,n);}if(x<A-1){if(g[y][x+1]==o)ff(x+1,y,o,n);}}
bool check_g(void)
{for(int i=0;i<6;i++){if(!memcmp(g,&t[i][0][0],sizeof(g)))return(true);}return(0);}
void is(char*s){char*c;c=s+strlen(s)-1;*c+=1;while(*c>'6'){*c='1';if (c==s){strcat(s,"1");return;}c--;*c+=1;}}
void pr(void)
{int x, y;for(y=0;y<A;y++){for(x=0;x<A;x++)printf("%d ",g[y][x]);printf("\n");}printf("\n");}
int main(void)
{for(int i=0;i<6;i++)memset(&t[i][0][0],i+1,A*A);char s[256]="0";memcpy(s,g,sizeof(g));pr();do{memcpy(g,s,sizeof(g));is(s);for(int i=0;i<strlen(s);i++){ff(4,4,g[4][4],s[i]-'0');}}while(!check_g());
pr();printf("%s\n",s);return 0;}

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

1
จนกว่าคุณจะสามารถหาจำนวนขั้นตอนที่แน่นอนได้ฉันก็ไม่สามารถยอมรับวิธีแก้ปัญหานี้ได้แม้ว่าจะเป็นวิธีที่ดีที่สุดก็ตาม
Joe Z.

นอกจากนี้ขนาดกริดคือ 19 ไม่ใช่ 9
โจซี

โอเคฉันกำหนดขนาดกริด ไม่มีใครรู้วิธีการคำนวณจำนวนขั้นต่ำตามทฤษฎีที่จำเป็น?
ผู้ใช้

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