用一连串的棒形成多米诺骨牌


20

背景

考虑一个(闭合的)杆链,每个杆的长度都是整数。有多少不同的无孔四角系统,你可以在给定链形成的?或者换句话说,对于给定的链,可以形成多少个与轴对齐的边的非自相交多边形?

让我们看一个例子。考虑由8条长度为1和2的杆组成的特定链,我们可以将其表示为[1, 1, 2, 2, 1, 1, 2, 2]。直到旋转和平移,只有8种可能的多米诺骨牌(我们会计算不同的反射):

在此处输入图片说明

第一个杆是深蓝色,然后我们以逆时针方向遍历多边形。

旋转感不影响上面示例中的结果。但是,让我们考虑另一条链,[3, 1, 1, 1, 2, 1, 1]它产生以下3个多氨基酸:

在此处输入图片说明

注意,我们包括最后一个多米诺骨牌的反射,因为它需要顺时针遍历。

如果我们有一条相同长度的更灵活的链[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],我们实际上将能够在其他一些多音色之间形成两种反射,总计为9:

在此处输入图片说明

挑战

给定链条的描述(以阵列或类似形式),可以按顺序确定杆(旋转和平移的情况下)可以形成的不同多义氨基酸的数量,同时按逆时针方向绕周长。

请编写完整的程序,并包含用于编译的命令(如果适用),并从命令行运行代码。另请提供指向您所用语言的免费编译器/解释器的链接。

您的程序应从STDIN读取输入。第一行将包含的整数中号。接下来的M行将是测试用例,每个用例将是一个用空格分隔的杆长度列表。您的程序应将M行打印到STDOUT,每行由一个整数组成-可以形成的不同多氨基酸的数量。

您只能使用一个线程。

您的程序在任何时候都不得使用超过1 GB的内存。(这不是一个完全严格的限制,但是我将监视可执行文件的内存使用情况,并杀死任何持续使用超过1 GB或峰值明显超过此1GB的进程。)

为防止过多的预计算,您的代码不得超过20,000字节,并且不得读取任何文件。

您也不能针对所选的特定测试用例进行优化(例如,通过对结果进行硬编码)。如果我怀疑您这样做,我保留生成新基准集的权利。测试集是随机的,因此您的程序在这些测试集上的性能应代表其在任意输入下的性能。您唯一可以做的假设是杆长度的总和是偶数。

计分

N = 10、11,...,20根杆的链条提供了基准套件。每个测试集包含50条随机链,长度在1-4之间(含1和4)。

您的主要分数是程序在5分钟内(在我的计算机上,Windows 8下)完成完整测试集的最大N分。决胜局将是程序在该测试集上花费的实际时间。

如果有人击败最大的测试仪,我将继续增加更大的测试仪。

测试用例

您可以使用以下测试用例来检查实现的正确性。

Input                            Output

1 1                              0
1 1 1 1                          1
1 1 1 1 1 1                      1
1 1 1 1 1 1 1 1                  3
1 1 1 1 1 1 1 1 1 1              9
1 1 1 1 1 1 1 1 1 1 1 1          36
1 1 1 1 1 1 1 1 1 1 1 1 1 1      157
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1  758
1 1 2 2 1 1 2 2                  8
1 1 2 2 1 1 2 2 1 1              23
1 1 2 2 1 1 2 2 1 1 2 2          69
1 2 1 2 1 2 1 2                  3
1 2 1 2 1 2 1 2 1 2 1 2          37
1 2 3 2 1 2 3 2                  5
1 2 3 2 1 2 3 2 1 2 3 2          23
3 1 1 1 2 1 1                    3
1 2 3 4 5 6 7                    1
1 2 3 4 5 6 7 8                  3
1 2 3 4 5 6 7 8 9 10 11          5
2 1 5 3 3 2 3 3                  4
4 1 6 5 6 3 1 4                  2
3 5 3 5 1 4 1 1 3                5
1 4 3 2 2 5 5 4 6                4
4 1 3 2 1 2 3 3 1 4              18
1 1 1 1 1 2 3 3 2 1              24
3 1 4 1 2 2 1 1 2 4 1 2          107
2 4 2 4 2 2 3 4 2 4 2 3          114

您可以在此处找到带有这些文件的输入文件。

排行榜

   User          Language       Max N      Time taken (MM:SS:mmm)

1. feersum       C++ 11         19         3:07:430

2. Sp3000        Python 3       18         2:30:181

“无孔”似乎是多余的。首先,一个连续的链不能产生带孔的多胺。
Sparr 2014年

是否允许多线程?而且,如果线程处于不同的进程中,那么每个线程都可以使用1 GB吗?:P
feersum 2014年

@Sparr当外围在一个角落碰触时可以。例如,请参阅此处的第81号。那不应该算在内。
Martin Ender 2014年

@feersum为简单起见,我将对多线程说不(并将对挑战进行编辑)。
Martin Ender 2014年

1
@PeterKagey您是否对错误的挑战发表了此评论?像它看起来应该去上这一个
Martin Ender

Answers:


5

C ++ 11

更新:添加了第一行,c如果距原点的距离太远,则第一行会中断(这是变量的全部用途rlen,但我忘了在第一版中写它)。我将其更改为使用更少的内存,但要花时间。现在,它对我来说只需不到5分钟即可解决N = 20。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <ctime>

#define M std::map
#define MS 999
#define l (xM*2+1)

#define KITTENS(A,B)A##B
#define CATS(A,B)KITTENS(A,B)
#define LBL CATS(LBL,__LINE__)
#define unt unsigned
#define SU sizeof(unt)
#define SUB (SU*8)
#define newa (nb^SZ||fail("blob"),nb+++blob)

#define D

struct vec {int x, y;};


unt s[MS*2];
int xM, sl[MS];
vec X[MS];

struct a;
struct a  { M<unt,unt>v;};

#define SZ ((1<<29)/sizeof(a))
a*blob;
unt nb;


int fail(const char*msg)
{
    printf("failed:%s", msg);
    exit(1);
    return 1;
}

struct
{
    unt*m;
    bool operator()(int x, int y) { return m[(x+l*y)/SUB] >> (x+l*y)%SUB & 1; }
    void one(int x, int y) { m[(x+l*y)/SUB] |= 1U << (x+l*y)%SUB; }
    void zero(int x, int y) { m[(x+l*y)/SUB] &= ~(1U << (x+l*y)%SUB); }
} g;

unt c(a*A, vec x, unt rlen, unt sn) {
    if((unt)x.y+abs(x.x) > rlen) return 0;
    if(!rlen) {
        vec *cl=X, *cr=X, *ct=X;
        for(unt i=1; i<sn; i++) {
            #define BLAH(Z,A,B,o,O) \
                if(X[i].A o Z->A || (X[i].A == Z->A && X[i].B O Z->B)) \
                   Z = X+i

            BLAH(cl,x,y,<,>);
            BLAH(cr,x,y,>,<);
            BLAH(ct,y,x,>,>);
        }
        unt syms = 1;
        #define BLA(H,Z) {bool sy=1;for(unt o=0; o<sn; o++) sy &= (int)(1|-(H))*sl[o] == sl[(Z-X+o)%sn]; syms += sy;}
        BLA(~o&1,cl)
        BLA(1,ct)
        BLA(o&1,cr)

        #ifdef D
            //printf("D");for(int i=0;i<sn;i++)printf(" %u",sl[i]);printf("\n");
            if(syms==3) fail("symm");
        #endif

        return syms;
    }
    if(!(x.x|x.y|!sn)) return 0;
    X[sn] = x;

    unt k = 0;
    for(auto it: A->v) {
        int len = it.first;
        bool ve = sn&1;
        int dx = ve?0:len, dy = ve?len:0;

        #define PPCG(O)(x.x O (ve?0:z), x.y O (ve?z:0))
        #define MACR(O) { \
            vec v2 = {x.x O dx, x.y O dy}; \
            if(v2.y<0||(!v2.y&&v2.x<0)||abs(v2.x)>xM||v2.y>xM) \
                goto LBL; \
            for(int z=1; z<=len; z++) \
                if(g PPCG(O)) \
                    goto LBL; \
            for(int z=1; z<=len; z++) \
                g.one PPCG(O); \
            sl[sn] = O len; \
            k += c(blob+it.second, v2, rlen - len, sn+1); \
            for(int z=1; z<=len; z++) \
                g.zero PPCG(O); \
            } LBL: \

    MACR(+);
    MACR(-);
    }

    return k;
}

void stuff(a *n, unt j, unt r, unt len1)
{
    unt t=0;
    for(unt i=j; i<j+r; i++) {
        t += s[i];
        if((int)t > xM || (len1 && t>len1)) break;
        if(len1 && t < len1) continue;
        int r2 = r-(i-j)-1;
        if(r2) {
            unt x;
            if(n->v.count(t))
                x = n->v[t];
            else
                n->v[t] = x = newa - blob;
            stuff(blob+x, i+1, r2, 0);
        } else n->v[t] = -1;
    }
}

int main()
{
    time_t tim = time(0);
    blob = new a[SZ];
    int n;
    scanf("%u",&n);
    while(n--) {
        nb = 0;
        unt ns=0, tl=0;
        while(scanf("%u",s+ns)) {
            tl += s[ns];
            if(++ns==MS) return 1;
            if(getchar() < 11) break;
        }
        xM = ~-tl/2;
        g.m = (unt*)calloc((xM+1)*l/SU + 1,4);

        memcpy(s+ns, s, ns*SU);

        unt ans = 0;
        for(unt len1 = 1; (int)len1 <= xM; len1++) {
            a* a0 = newa;
            for(unt i=0; i<ns; i++)
                stuff(a0, i, ns, len1);
            ans += c(a0, {}, tl, 0);
            for(unt i=0; i<nb; i++)
                blob[i].v.clear();
        }
        printf("%d\n", ans/4);
        free(g.m);
    }

    tim = time(0) - tim;
    printf("time:%d",(int)tim);
    return 0;
}

编译

g++ --std=c++11 -O3 feersum.cpp -o feersum.exe

打ze #define
Soham Chowdhury 2014年

在没有其他答案的情况下...这里有赏金!
Sp3000

3

Python 3(带有PyPy)— N = 18

ANGLE_COMPLEMENTS = {"A": "C", "F": "F", "C": "A"}
MOVE_ENUMS = {"U": 0, "R": 1, "D": 2, "L": 3}
OPPOSITE_DIR = {"U": "D", "D": "U", "L": "R", "R": "L", "": ""}

def canonical(angle_str):
    return min(angle_str[i:] + angle_str[:i] for i in range(len(angle_str)))

def to_angles(moves):
    """
    Convert a string of UDLR to a string of angles where
      A -> anticlockwise turn
      C -> clockwise turn
      F -> forward
    """

    angles = []

    for i in range(1, len(moves)):
        if moves[i] == moves[i-1]:
            angles.append("F")
        elif (MOVE_ENUMS[moves[i]] - MOVE_ENUMS[moves[i-1]]) % 4 == 1:
            angles.append("C")
        else:
            angles.append("A")

    if moves[0] == moves[len(moves)-1]:
        angles.append("F")
    elif (MOVE_ENUMS[moves[0]] - MOVE_ENUMS[moves[len(moves)-1]]) % 4 == 1:
        angles.append("C")
    else:
        angles.append("A")

    return "".join(angles)

def solve(rods):
    FOUND_ANGLE_STRS = set()

    def _solve(rods, rod_sum, point=(0, 0), moves2=None, visited=None, last_dir=""):
        # Stop when point is too far from origin
        if abs(point[0]) + abs(point[1]) > rod_sum:
            return

        # No more rods, check if we have a valid solution
        if not rods:
            if point == (0, 0):
               angle_str = to_angles("".join(moves2))

               if angle_str.count("A") - angle_str.count("C") == 4:
                   FOUND_ANGLE_STRS.add(canonical(angle_str))

            return

        r = rods.pop(0)

        if not visited:
            visited = set()
            move_dirs = [((r, 0), "R")]
            moves2 = []

        else:
            move_dirs = [((r,0), "R"), ((0,r), "U"), ((-r,0), "L"), ((0,-r), "D")]

        opp_dir = OPPOSITE_DIR[last_dir]

        for move, direction in move_dirs:
            if direction == opp_dir: continue

            new_point = (move[0] + point[0], move[1] + point[1])
            added_visited = set()
            search = True

            for i in range(min(point[0],new_point[0]), max(point[0],new_point[0])+1):
                for j in range(min(point[1],new_point[1]), max(point[1],new_point[1])+1):
                    if (i, j) != point:
                        if (i, j) in visited:
                            search = False

                            for a in added_visited:
                                visited.remove(a)

                            added_visited = set()                            
                            break

                        else:
                            visited.add((i, j))
                            added_visited.add((i, j))

                if not search:
                    break

            if search:
                moves2.append(direction*r)
                _solve(rods, rod_sum-r, new_point, moves2, visited, direction)
                moves2.pop()

            for a in added_visited:
                visited.remove(a)

        rods.insert(0, r)
        return

    _solve(rods, sum(rods))
    return len(FOUND_ANGLE_STRS)

num_rods = int(input())

for i in range(num_rods):
    rods = [int(x) for x in input().split(" ")]
    print(solve(rods))

用运行./pypy <filename>


这是我在与Martin讨论问题时编写的参考实现。它的设计并非一意孤行,而且很hacky,但它应该为开始工作提供良好的基准。

在我普通的笔记本电脑上,N = 18大约需要2.5分钟。

算法

通过将每个形状转换为一系列形状来检查旋转,这些形状在形状边界上的每个晶格点处分别F为正向,A逆时针旋转和C顺时针旋转-我称之为角度字符串。如果两个形状的角度字符串为循环排列,则它们在旋转上相同。当我们找到一个新形状时,我们不总是通过直接比较两个角度字符串来进行检查,而是在存储之前将其转换为规范形式。当我们有一个新的候选者时,我们将转换为规范形式,并检查是否已经具有了这种形式(因此可以利用哈希,而不是遍历整个集合)。

通过确保As的数量超过s的数量C4 ,角度字符串也可用于检查形状是否为逆时针方向。

通过将每个晶格点存储在形状的边界上,并查看某个点是否被访问两次,可以很自然地检查自相交。

核心算法很简单,将第一个杆放在右边,然后尝试所有剩余杆的所有可能性。如果杆到达的点距离原点太远(即杆剩余长度的总和小于该点到原点的曼哈顿距离),那么我们会过早停止搜索该子树。

更新(最新发布)

  • 6/12:引入规范形式,添加了一些微优化
  • 5/12:修复了算法说明中的错误。使用B + B方法的A子串(使用A,B循环置换)使二次循环置换检查算法线性化(我不知道为什么我之前没有这样做)。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.