编程竞赛问题:计数多项式

时间:2011-01-10 19:41:09

标签: c# java c++ algorithm

请看我自己的答案,我想我做到了!


您好,

编程竞赛的一个示例问题是编写一个程序,找出在给定数量的宝石中可能有多少多边形。

因此,对于两块石头(n = 2),只有一个多边形:

XX

您可能认为这是第二种解决方案:

X
X

但事实并非如此。如果您可以旋转多边形,则它们不是唯一的。

因此,对于4个宝石(n = 4),有7个解决方案:

X
X   XX   X    X     X   X
X   X    XX   X    XX   XX   XX
X   X    X    XX   X     X   XX

应用程序必须能够找到1 <= n <=10

的解决方案

PS:不允许使用list of polyominos on Wikipedia;)

编辑:当然问题是:如何在Java,C / C ++,C#

中做到这一点

我用Java开始这个项目。但后来我不得不承认我不知道如何使用有效的算法构建多项式。

这是我到目前为止所做的:

import java.util.ArrayList;
import java.util.List;


public class Main
{

    private int countPolyminos(int n)
    {
        hashes.clear();
        count = 0;
        boolean[][] matrix = new boolean[n][n];
        createPolyominos(matrix, n);
        return count;
    }

    private List<Integer> hashes = new ArrayList<Integer>();
    private int count;

    private void createPolyominos(boolean[][] matrix, int n)
    {
        if (n == 0)
        {
            boolean[][] cropped = cropMatrix(matrix); 
            int hash = hashMatrixOrientationIndependent(matrix);
            if (!hashes.contains(hash))
            {
                count++;
                hashes.add(hash);
            }
            return;
        }
    // Here is the real trouble!!
    // Then here something like; createPolyominos(matrix, n-1);
    // But, we need to keep in mind that the polyominos can have ramifications
    }

    public boolean[][] copy(boolean[][] matrix)
    {
        boolean[][] b = new boolean[matrix.length][matrix[0].length];
        for (int i = 0; i < matrix.length; ++i)
        {
            System.arraycopy(matrix[i], 0, b, 0, matrix[i].length);
        }
        return b;
    }

    public boolean[][] cropMatrix(boolean[][] matrix)
    {
        int l = 0, t = 0, r = 0, b = 0;
        // Left
        left: for (int x = 0; x < matrix.length; ++x)
        {
            for (int y = 0; y < matrix[x].length; ++y)
            {
                if (matrix[x][y])
                {
                    break left;
                }
            }
            l++;
        }
        // Right
        right: for (int x = matrix.length - 1; x >= 0; --x)
        {
            for (int y = 0; y < matrix[x].length; ++y)
            {
                if (matrix[x][y])
                {
                    break right;
                }
            }
            r++;
        }
        // Top
        top: for (int y = 0; y < matrix[0].length; ++y)
        {
            for (int x = 0; x < matrix.length; ++x)
            {
                if (matrix[x][y])
                {
                    break top;
                }
            }
            t++;
        }
        // Bottom
        bottom: for (int y = matrix[0].length; y >= 0; --y)
        {
            for (int x = 0; x < matrix.length; ++x)
            {
                if (matrix[x][y])
                {
                    break bottom;
                }
            }
            b++;
        }

        // Perform the real crop
        boolean[][] cropped = new boolean[matrix.length - l - r][matrix[0].length - t - b];
        for (int x = l; x < matrix.length - r; ++x)
        {
            System.arraycopy(matrix[x - l], t, cropped, 0, matrix[x].length - t - b);
        }
        return cropped;
    }

    public int hashMatrix(boolean[][] matrix)
    {
        int hash = 0;
        for (int x = 0; x < matrix.length; ++x)
        {
            for (int y = 0; y < matrix[x].length; ++y)
            {
                hash += matrix[x][y] ? (((x + 7) << 4) * ((y + 3) << 6) * 31) : ((((x+5) << 9) * (((y + x) + 18) << 7) * 53));
            }
        }
        return hash;
    }

    public int hashMatrixOrientationIndependent(boolean[][] matrix)
    {
        int hash = 0;
        hash += hashMatrix(matrix);
        for (int i = 0; i < 3; ++i)
        {
            matrix = rotateMatrixLeft(matrix);
            hash += hashMatrix(matrix);
        }
        return hash;
    }

    public boolean[][] rotateMatrixRight(boolean[][] matrix)
    {
        /* W and H are already swapped */
        int w = matrix.length;
        int h = matrix[0].length;
        boolean[][] ret = new boolean[h][w];
        for (int i = 0; i < h; ++i)
        {
            for (int j = 0; j < w; ++j)
            {
                ret[i][j] = matrix[w - j - 1][i];
            }
        }
        return ret;
    }

    public boolean[][] rotateMatrixLeft(boolean[][] matrix)
    {
        /* W and H are already swapped */
        int w = matrix.length;
        int h = matrix[0].length;
        boolean[][] ret = new boolean[h][w];
        for (int i = 0; i < h; ++i)
        {
            for (int j = 0; j < w; ++j)
            {
                ret[i][j] = matrix[j][h - i - 1];
            }
        }
        return ret;
    }

}

7 个答案:

答案 0 :(得分:6)

只有4,461个大小为10的多项式,所以我们可以全部枚举它们。

从一块石头开始。要将它扩展到一块石头,请尝试在与现有石头相邻的所有空单元中添加新石头。递归执行此操作直到达到所需的大小。

为了避免重复,请保留我们已经枚举的每个大小的所有多项式的哈希表。当我们组合一个新的多项式时,我们检查它不在哈希表中。我们还需要检查它的3个旋转(可能还有它的镜像)。虽然最终大小的重复检查是唯一严格必要的检查,但检查每一步都会修剪将产生新多项式的递归分支。

这是一些伪代码:

polynomino = array of n hashtables
function find_polynominoes(n, base):
  if base.size == n:
    return
  for stone in base:
    for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
      new_stone.x = stone.x + dx
      new_stone.y = stone.y + dy
      if new_stone not in base:
        new_polynomino = base + new_stone
        is_new = true
        for rotation in [0, 90, 180, 270]:
          if new_polynomino.rotate(rotation) in polynomino[new_polynomino.size]:
            is_new = false
            break
        if is_new:
          polynomino[new_polynomino.size].add(new_polynomino)

答案 1 :(得分:3)

最天真的解决方案是从单个X开始,并且对于每次迭代,构建唯一可能的下一状态列表。从该列表中,通过添加另一个X来构建唯一状态列表。继续这个直到你想要的迭代。

然而,我不确定这是否在N = 10的合理时间内运行。它可能会根据您的要求而定。

答案 2 :(得分:2)

刚刚在java中解决了这个问题。因为这里似乎都有性能问题。我也把你给了我。

董事会代表:

2个整数数组。 1表示行,1表示列。

  • 轮换: column[i]=row[size-(i+1)]row[i] = reverse(column[i])其中,反向是根据大小反转的位(对于大小= 4和前2位:rev(1100) = 0011
  • 移动块: row[i-1] = row[i]col[i]<<=1
  • 检查位是否已设置: (row[r] & (1<<c)) > 0
  • 电路板唯一性:当数组行是唯一的时,电路板是唯一的。
  • Board hash:数组行的Hashcode
  • ..

因此,这使得所有操作都很快。他们中的许多人在二维数组表示中都是O(size²),而不是现在O(size)

<强>算法:

  • 从1号块开始
  • 每块尺寸从1块石头开始。
  • 如果可以添加石头。检查它是否已添加到集合中。
  • 如果尚未添加。将其添加到此大小的解决方案中。
    • 将块添加到集合及其所有旋转。 (3次轮换,共4次)
    • 重要的是,每次旋转后,尽可能将块移动到左侧/顶部。
  • +特殊情况:为接下来的两个案例做同样的逻辑
    • 向右移动一个块并在第一列添加石头
    • 将一个块移到底部并在第一行添加石头

<强>性能:

  • N=5,时间:3毫秒
  • N=10,时间:58毫秒
  • N=11,时间:166毫秒
  • N=12,时间:538毫秒
  • N=13,时间:2893毫秒
  • N=14,时间:17266ms
  • N=15,NA(在堆空间之外)

代码: https://github.com/Samjayyy/logicpuzzles/tree/master/polyominos

答案 3 :(得分:1)

我想我做到了!

编辑:我正在使用SHA-256算法对它们进行哈希处理,现在它的工作正常。

结果如下:

numberOfStones -> numberOfPolyominos
            1  -> 1
            2  -> 1
            3  -> 2
            4  -> 7
            5  -> 18
            6  -> 60
            7  -> 196
            8  -> 704
            9  -> 2500
            10 -> terminated

这是代码(Java):

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

/* VPW Template */

public class Main
{

    /**
     * @param args
     */
    public static void main(String[] args) throws IOException
    {
        new Main().start();
    }

public void start() throws IOException
{

    /* Read the stuff */
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String[] input = new String[Integer.parseInt(br.readLine())];
    for (int i = 0; i < input.length; ++i)
    {
        input[i] = br.readLine();
    }
    /* Process each line */
    for (int i = 0; i < input.length; ++i)
    {
        processLine(input[i]);
    }
}

public void processLine(String line)
{
    int n = Integer.parseInt(line);
    System.out.println(countPolyminos(n));
}

private int countPolyminos(int n)
{
    hashes.clear();
    count = 0;
    boolean[][] matrix = new boolean[n][n];
    matrix[n / 2][n / 2] = true;
    createPolyominos(matrix, n - 1);
    return count;
}

private List<BigInteger> hashes = new ArrayList<BigInteger>();
private int count;

private void createPolyominos(boolean[][] matrix, int n)
{
    if (n == 0)
    {
        boolean[][] cropped = cropMatrix(matrix);
        BigInteger hash = hashMatrixOrientationIndependent(cropped);
        if (!hashes.contains(hash))
        {
            // System.out.println(count + " Found!");
            // printMatrix(cropped);
            // System.out.println();
            count++;
            hashes.add(hash);
        }
        return;
    }
    for (int x = 0; x < matrix.length; ++x)
    {
        for (int y = 0; y < matrix[x].length; ++y)
        {
            if (matrix[x][y])
            {
                if (x > 0 && !matrix[x - 1][y])
                {
                    boolean[][] clone = copy(matrix);
                    clone[x - 1][y] = true;
                    createPolyominos(clone, n - 1);
                }
                if (x < matrix.length - 1 && !matrix[x + 1][y])
                {
                    boolean[][] clone = copy(matrix);
                    clone[x + 1][y] = true;
                    createPolyominos(clone, n - 1);
                }
                if (y > 0 && !matrix[x][y - 1])
                {
                    boolean[][] clone = copy(matrix);
                    clone[x][y - 1] = true;
                    createPolyominos(clone, n - 1);
                }
                if (y < matrix[x].length - 1 && !matrix[x][y + 1])
                {
                    boolean[][] clone = copy(matrix);
                    clone[x][y + 1] = true;
                    createPolyominos(clone, n - 1);
                }
            }
        }
    }
}

public boolean[][] copy(boolean[][] matrix)
{
    boolean[][] b = new boolean[matrix.length][matrix[0].length];
    for (int i = 0; i < matrix.length; ++i)
    {
        System.arraycopy(matrix[i], 0, b[i], 0, matrix[i].length);
    }
    return b;
}

public void printMatrix(boolean[][] matrix)
{
    for (int y = 0; y < matrix.length; ++y)
    {
        for (int x = 0; x < matrix[y].length; ++x)
        {
            System.out.print((matrix[y][x] ? 'X' : ' '));
        }
        System.out.println();
    }
}

public boolean[][] cropMatrix(boolean[][] matrix)
{
    int l = 0, t = 0, r = 0, b = 0;
    // Left
    left: for (int x = 0; x < matrix.length; ++x)
    {
        for (int y = 0; y < matrix[x].length; ++y)
        {
            if (matrix[x][y])
            {
                break left;
            }
        }
        l++;
    }
    // Right
    right: for (int x = matrix.length - 1; x >= 0; --x)
    {
        for (int y = 0; y < matrix[x].length; ++y)
        {
            if (matrix[x][y])
            {
                break right;
            }
        }
        r++;
    }
    // Top
    top: for (int y = 0; y < matrix[0].length; ++y)
    {
        for (int x = 0; x < matrix.length; ++x)
        {
            if (matrix[x][y])
            {
                break top;
            }
        }
        t++;
    }
    // Bottom
    bottom: for (int y = matrix[0].length - 1; y >= 0; --y)
    {
        for (int x = 0; x < matrix.length; ++x)
        {
            if (matrix[x][y])
            {
                break bottom;
            }
        }
        b++;
    }

    // Perform the real crop
    boolean[][] cropped = new boolean[matrix.length - l - r][matrix[0].length - t - b];
    for (int x = l; x < matrix.length - r; ++x)
    {
        System.arraycopy(matrix[x], t, cropped[x - l], 0, matrix[x].length - t - b);
    }
    return cropped;
}

public BigInteger hashMatrix(boolean[][] matrix)
{
    try
    {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update((byte) matrix.length);
        md.update((byte) matrix[0].length);
        for (int x = 0; x < matrix.length; ++x)
        {
            for (int y = 0; y < matrix[x].length; ++y)
            {
                if (matrix[x][y])
                {
                    md.update((byte) x);
                } else
                {
                    md.update((byte) y);
                }
            }
        }
        return new BigInteger(1, md.digest());
    } catch (NoSuchAlgorithmException e)
    {
        System.exit(1);
        return null;
    }
}

public BigInteger hashMatrixOrientationIndependent(boolean[][] matrix)
{
    BigInteger hash = hashMatrix(matrix);
    for (int i = 0; i < 3; ++i)
    {
        matrix = rotateMatrixLeft(matrix);
        hash = hash.add(hashMatrix(matrix));
    }
    return hash;
}

public boolean[][] rotateMatrixRight(boolean[][] matrix)
{
    /* W and H are already swapped */
    int w = matrix.length;
    int h = matrix[0].length;
    boolean[][] ret = new boolean[h][w];
    for (int i = 0; i < h; ++i)
    {
        for (int j = 0; j < w; ++j)
        {
            ret[i][j] = matrix[w - j - 1][i];
        }
    }
    return ret;
}

public boolean[][] rotateMatrixLeft(boolean[][] matrix)
{
    /* W and H are already swapped */
    int w = matrix.length;
    int h = matrix[0].length;
    boolean[][] ret = new boolean[h][w];
    for (int i = 0; i < h; ++i)
    {
        for (int j = 0; j < w; ++j)
        {
            ret[i][j] = matrix[j][h - i - 1];
        }
    }
    return ret;
}

答案 4 :(得分:1)

这是我在Java中解决同样问题的方法。我可以确认Martijn的数字(见下文)。我还在粗略的时间内添加了计算结果(2012年中期的Macbook Retina Core i7)。我认为通过并行化可以实现显着的性能提升。

numberOfStones -> numberOfPolyominos
        1  -> 1
        2  -> 1
        3  -> 2
        4  -> 7
        5  -> 18
        6  -> 60
        7  -> 196
        8  -> 704      (3 seconds)
        9  -> 2500     (46 seconds)
        10 -> 9189     (~14 minutes)

    /*
 * This class is a solution to the Tetris unique shapes problem. 
 * That is, the game of Tetris has 7 unique shapes. These 7 shapes
 * are all the possible unique combinations of any 4 adjoining blocks
 * (i.e. ignoring rotations).
 * 
 * How many unique shapes are possible with, say, 7 or n blocks? 
 * 
 * The solution uses recursive back-tracking to construct all the possible
 * shapes. It uses a HashMap to store unique shapes and to ignore rotations.
 * It also uses a temporary HashMap so that the program does not needlessly
 * waste time checking the same path multiple times.
 * 
 * Even so, this is an exponential run-time solution, with n=10 taking a few
 * minutes to complete.
 */
package com.glugabytes.gbjutils;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class TetrisBlocks {

    private HashMap uShapes;
    private HashMap tempShapes;

    /* Get a map of unique shapes for n squares. The keys are string-representations
     * of each shape, and values are corresponding boolean[][] arrays.
     * @param squares - number of blocks to use for shapes, e.g. n=4 has 7 unique shapes
     */
    public Map getUniqueShapes(int squares) {
        uShapes = new HashMap();
        tempShapes = new HashMap();

        boolean[][] data = new boolean[squares*2+1][squares*2+1];
        data[squares][squares] = true;
        make(squares, data, 1);             //start the process with a single square in the center of a boolean[][] matrix

        return uShapes;
    }

    /* Recursivelly keep adding blocks to the data array until number of blocks(squares) = required size (e.g. n=4)
     * Make sure to eliminate rotations. Also make sure not to enter infinite backtracking loops, and also not 
     * needlessly recompute the same path multiple times.
     */
    private void make(int squares, boolean[][] data, int size) {
        if(size == squares) {   //used the required number of squares
            //get a trimmed version of the array
            boolean[][] trimmed = trimArray(data);
            if(!isRotation(trimmed)) {  //if a unique piece, add it to unique map
                uShapes.put(arrayToString(trimmed), trimmed);
            }

        } else {
            //go through the grid 1 element at a time and add a block next to an existing block
            //do this for all possible combinations
            for(int iX = 0; iX < data.length; iX++) {
                for(int iY = 0; iY < data.length; iY++) {
                    if(data[iX][iY] == true) {      //only add a block next to an existing block
                        if(data[iX+1][iY] != true) {    //if no existing block to the right, add one and recuse
                            data[iX+1][iY] = true;
                            if(!isTempRotation(data)) {         //only recurse if we haven't already been on this path before
                                make(squares, data, size+1);
                                tempShapes.put(arrayToString(data), data);  //store this path so we don't repeat it later
                            }
                            data[iX+1][iY] = false; 
                        }

                        if(data[iX-1][iY] != true) {    //repeat by adding a block on the left
                            data[iX-1][iY] = true;      
                            if(!isTempRotation(data)) {
                                make(squares, data, size+1);
                                tempShapes.put(arrayToString(data), data);
                            }
                            data[iX-1][iY] = false; 
                        }

                        if(data[iX][iY+1] != true) {   //repeat by adding a block down
                            data[iX][iY+1] = true;      
                            if(!isTempRotation(data)) {
                                make(squares, data, size+1);
                                tempShapes.put(arrayToString(data), data);
                            }
                            data[iX][iY+1] = false; 
                        }

                        if(data[iX][iY-1] != true) {   //repeat by adding a block up
                            data[iX][iY-1] = true;      
                            if(!isTempRotation(data)) {
                                make(squares, data, size+1);
                                tempShapes.put(arrayToString(data), data);
                            }
                            data[iX][iY-1] = false; 
                        }
                    }
                }
            }
        }
    }

    /**
     * This function basically removes all rows and columns that have no 'true' flags,
     * leaving only the portion of the array that contains useful data.
     * 
     * @param data
     * @return 
     */
    private boolean[][] trimArray(boolean[][] data) {
        int maxX = 0;
        int maxY = 0;
        int firstX = data.length;
        int firstY = data.length;

        for(int iX = 0; iX < data.length; iX++) {
            for (int iY = 0; iY < data.length; iY++) {
                if(data[iX][iY]) {
                    if(iY < firstY) firstY = iY;
                    if(iY > maxY) maxY = iY;
                }
            }
        }

        for(int iY = 0; iY < data.length; iY++) {
            for (int iX = 0; iX < data.length; iX++) {
                if(data[iX][iY]) {
                    if(iX < firstX) firstX = iX;
                    if(iX > maxX) maxX = iX;
                }
            }
        }



        boolean[][] trimmed = new boolean[maxX-firstX+1][maxY-firstY+1];
            for(int iX = firstX; iX <= maxX; iX++) {
                for(int iY = firstY; iY <= maxY; iY++) {
                    trimmed[iX-firstX][iY-firstY] = data[iX][iY];
                }
            }

        return trimmed;
    }

    /**
     * Return a string representation of the 2D array.
     * 
     * @param data
     * @return 
     */
    private String arrayToString(boolean[][] data) {
        StringBuilder sb = new StringBuilder();
        for(int iX = 0; iX < data.length; iX++) {
            for(int iY = 0; iY < data[0].length; iY++) {
                sb.append(data[iX][iY] ? '#' : ' ');
            }
            sb.append('\n');
        }

        return sb.toString();
    }

    /**
     * Rotate an array clockwise by 90 degrees.
     * @param data
     * @return 
     */
    public boolean[][] rotate90(boolean[][] data) {
        boolean[][] rotated = new boolean[data[0].length][data.length];

        for(int iX = 0; iX < data.length; iX++) {
            for(int iY = 0; iY < data[0].length; iY++) {
                rotated[iY][iX] = data[data.length - iX - 1][iY];
            }
        }

        return rotated;
    }

    /**
     * Checks to see if two 2d boolean arrays are the same
     * @param a
     * @param b
     * @return 
     */
    public boolean equal(boolean[][] a, boolean[][] b) {
        if(a.length != b.length || a[0].length != b[0].length) {
            return false;
        } else {
            for(int iX = 0; iX < a.length; iX++) {
                for(int iY = 0; iY < a[0].length; iY++) {
                    if(a[iX][iY] != b[iX][iY]) {
                        return false;
                    }
                }
            }
        }

        return true;
    }

    public boolean isRotation(boolean[][] data) {
        //check to see if it's a rotation of a shape that we already have
        data = rotate90(data);                      //+90*
        String str = arrayToString(data);
        if(!uShapes.containsKey(str)) {
            data = rotate90(data);                  //180*
            str = arrayToString(data);
            if(!uShapes.containsKey(str)) {
                data = rotate90(data);              //270*
                str = arrayToString(data);
                if(!uShapes.containsKey(str)) {
                    return false;
                }
            }
        }
        return true;
    }

    public boolean isTempRotation(boolean[][] data) {
        //check to see if it's a rotation of a shape that we already have
        data = rotate90(data);                      //+90*
        String str = arrayToString(data);
        if(!tempShapes.containsKey(str)) {
            data = rotate90(data);                  //180*
            str = arrayToString(data);
            if(!tempShapes.containsKey(str)) {
                data = rotate90(data);              //270*
                str = arrayToString(data);
                if(!tempShapes.containsKey(str)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        TetrisBlocks tetris = new TetrisBlocks();
        long start = System.currentTimeMillis();
        Map shapes = tetris.getUniqueShapes(8);
        long end = System.currentTimeMillis();

        Iterator it = shapes.keySet().iterator();
        while(it.hasNext()) {
            String shape = (String)it.next();
            System.out.println(shape);
        }

        System.out.println("Unique Shapes: " + shapes.size());
        System.out.println("Time: " + (end-start));
    }
}

答案 5 :(得分:0)

这是一些计算答案的python。似乎同意维基百科。它不是非常快,因为它使用了大量的数组搜索而不是哈希表,但它仍然只需要一分钟左右即可完成。

#!/usr/bin/python                                                                                                                                                 

# compute the canonical representation of polyomino p.                                                                                                            
# (minimum x and y coordinate is zero, sorted)                                                                                                                    
def canonical(p):
  mx = min(map(lambda v: v[0], p))
  my = min(map(lambda v: v[1], p))
  return sorted(map(lambda v: (v[0]-mx, v[1]-my), p))

# rotate p 90 degrees                                                                                                                                               
def rotate(p):
  return canonical(map(lambda v: (v[1], -v[0]), p))

# add one tile to p                                                                                                                                               
def expand(p):
  result = []
  for (x,y) in p:
    for (dx,dy) in ((-1,0),(1,0),(0,-1),(0,1)):
      if p.count((x+dx,y+dy)) == 0:
        result.append(canonical(p + [(x+dx,y+dy)]))
  return result

polyominos = [[(0,0)]]

for i in xrange(1,10):
  new_polyominos = []
  for p in polyominos:
    for q in expand(p):
      dup = 0
      for r in xrange(4):
        if new_polyominos.count(q) != 0:
          dup = 1
          break
        q = rotate(q)
      if not dup: new_polyominos.append(q)
  polyominos = new_polyominos
  print i+1, len(polyominos)

答案 6 :(得分:0)

这是我完整的Python解决方案,灵感来自@marcog的答案。它会在我的笔记本电脑上大约2秒钟内打印出大小为2..10的多米诺骨牌。

算法很简单:

  • 尺寸1:从一个正方形开始
  • 大小%:获取所有大小为n + 1的片段,然后尝试将一个正方形添加到所有可能的相邻位置。这样,您就可以拥有所有可能的新作品。跳过重复项。

主要的加速来自散列片段,以快速检查是否已经看到片段。

n