计算Hankelable矩阵的数量


12

背景

二元汉克尔矩阵是具有仅包含0s和1s的恒定斜对角线(正倾斜对角线)的矩阵。例如,一个5x5二进制汉克矩阵看起来像

a b c d e
b c d e f
c d e f g
d e f g h
e f g h i

其中a, b, c, d, e, f, g, h, i要么01

让我们定义一个矩阵中号Hankelable如果有行和列的顺序的排列中号,使中号是汉克尔矩阵。这意味着可以对行顺序应用一种排列,对列应用一种可能的排列。

挑战

我们面临的挑战是要算多少Hankelable n通过n矩阵有全部n达到尽可能大的价值成为可能。

输出量

对于每个整数n从1个向上,输出的数目Hankelable n通过n基质与作为条目01

对于n = 1,2,3,4,5答案应该是2,12,230,12076,1446672。(感谢orlp提供产生这些代码的代码。)

时限

我将在计算机上运行您的代码,并在1分钟后将其停止。输出正确答案的代码,最大n次取胜。时间限制是所有问题的答案,从n = 1最大的价值到n您给出的答案。

优胜者将是4月18日星期六结束时的最佳答案。

决胜局

在平局的情况下,n我将等待多长时间才能使输出达到最高,n+1而最快的是获胜。如果它们在同一秒内运行到第二秒n+1,则第一个提交将获胜。

语言和图书馆

您可以使用任何具有免费编译器/解释器/等的语言。适用于Linux以及任何可免费用于Linux的库。

我的机器

计时将在我的机器上运行。这是在华硕M5A78L-M / USB3主板(插槽AM3 +,8GB DDR3)上的AMD FX-8350八核处理器上安装的标准ubuntu。这也意味着我需要能够运行您的代码。因此,请仅使用易于使用的免费软件,并请提供有关如何编译和运行代码的完整说明。

笔记

我建议不要遍历n个n个矩阵,并尝试检测每个矩阵是否具有我描述的属性。首先,太多了,其次,似乎没有快速的方法来进行这种检测

到目前为止的领先作品

  • 彼得·泰勒(Peter Taylor),n = 8。爪哇
  • n = 5(按奥尔普)。蟒蛇

4
我真的很喜欢“ Hankelable”这个词。
Alex A.

3
对于n=6总的是260357434。我认为内存压力是比CPU时间更大的问题。
彼得·泰勒

这是一个了不起的问题。我被书呆子彻底透了。
亚历山大·布雷特

Answers:


7

Java(n = 8)

import java.util.*;
import java.util.concurrent.*;

public class HankelCombinatorics {
    public static final int NUM_THREADS = 8;

    private static final int[] FACT = new int[13];
    static {
        FACT[0] = 1;
        for (int i = 1; i < FACT.length; i++) FACT[i] = i * FACT[i-1];
    }

    public static void main(String[] args) {
        long prevElapsed = 0, start = System.nanoTime();
        for (int i = 1; i < 12; i++) {
            long count = count(i), elapsed = System.nanoTime() - start;
            System.out.format("%d in %dms, total elapsed %dms\n", count, (elapsed - prevElapsed) / 1000000, elapsed / 1000000);
            prevElapsed = elapsed;
        }
    }

    @SuppressWarnings("unchecked")
    private static long count(int n) {
        int[][] perms = new int[FACT[n]][];
        genPermsInner(0, 0, new int[n], perms, 0);

        // We partition by canonical representation of the row sum multiset, discarding any with a density > 50%.
        Map<CanonicalMatrix, Map<CanonicalMatrix, Integer>> part = new HashMap<CanonicalMatrix, Map<CanonicalMatrix, Integer>>();
        for (int m = 0; m < 1 << (2*n-1); m++) {
            int density = 0;
            int[] key = new int[n];
            for (int i = 0; i < n; i++) {
                key[i] = Integer.bitCount((m >> i) & ((1 << n) - 1));
                density += key[i];
            }
            if (2 * density <= n * n) {
                CanonicalMatrix _key = new CanonicalMatrix(key);
                Map<CanonicalMatrix, Integer> map = part.get(_key);
                if (map == null) part.put(_key, map = new HashMap<CanonicalMatrix, Integer>());
                map.put(new CanonicalMatrix(m, perms[0]), m);
            }
        }

        List<Job> jobs = new ArrayList<Job>();
        ExecutorService pool = Executors.newFixedThreadPool(NUM_THREADS);

        for (Map.Entry<CanonicalMatrix, Map<CanonicalMatrix, Integer>> e : part.entrySet()) {
            Job job = new Job(n, perms, e.getKey().sum() << 1 == n * n ? 0 : 1, e.getValue());
            jobs.add(job);
            pool.execute(job);
        }

        pool.shutdown();
        try {
            pool.awaitTermination(1, TimeUnit.DAYS); // i.e. until it's finished - inaccurate results are useless
        }
        catch (InterruptedException ie) {
            throw new IllegalStateException(ie);
        }

        long total = 0;
        for (Job job : jobs) total += job.subtotal;
        return total;
    }

    private static int genPermsInner(int idx, int usedMask, int[] a, int[][] perms, int off) {
        if (idx == a.length) perms[off++] = a.clone();
        else for (int i = 0; i < a.length; i++) {
            int m = 1 << (a[idx] = i);
            if ((usedMask & m) == 0) off = genPermsInner(idx+1, usedMask | m, a, perms, off);
        }
        return off;
    }

    static class Job implements Runnable {
        private volatile long subtotal = 0;
        private final int n;
        private final int[][] perms;
        private final int shift;
        private final Map<CanonicalMatrix, Integer> unseen;

        public Job(int n, int[][] perms, int shift, Map<CanonicalMatrix, Integer> unseen) {
            this.n = n;
            this.perms = perms;
            this.shift = shift;
            this.unseen = unseen;
        }

        public void run() {
            long result = 0;
            int[][] perms = this.perms;
            Map<CanonicalMatrix, Integer> unseen = this.unseen;
            while (!unseen.isEmpty()) {
                int m = unseen.values().iterator().next();
                Set<CanonicalMatrix> equiv = new HashSet<CanonicalMatrix>();
                for (int[] perm : perms) {
                    CanonicalMatrix canonical = new CanonicalMatrix(m, perm);
                    if (equiv.add(canonical)) {
                        result += canonical.weight() << shift;
                        unseen.remove(canonical);
                    }
                }
            }

            subtotal = result;
        }
    }

    static class CanonicalMatrix {
        private final int[] a;
        private final int hash;

        public CanonicalMatrix(int m, int[] r) {
            this(permuteRows(m, r));
        }

        public CanonicalMatrix(int[] a) {
            this.a = a;
            Arrays.sort(a);

            int h = 0;
            for (int i : a) h = h * 37 + i;
            hash = h;
        }

        private static int[] permuteRows(int m, int[] perm) {
            int[] cols = new int[perm.length];
            for (int i = 0; i < perm.length; i++) {
                for (int j = 0; j < cols.length; j++) cols[j] |= ((m >> (perm[i] + j)) & 1L) << i;
            }
            return cols;
        }

        public int sum() {
            int sum = 0;
            for (int i : a) sum += i;
            return sum;
        }

        public int weight() {
            int prev = -1, count = 0, weight = FACT[a.length];
            for (int col : a) {
                if (col == prev) weight /= ++count;
                else {
                    prev = col;
                    count = 1;
                }
            }
            return weight;
        }

        @Override public boolean equals(Object obj) {
            // Deliberately unsuitable for general-purpose use, but helps catch bugs faster.
            CanonicalMatrix that = (CanonicalMatrix)obj;
            for (int i = 0; i < a.length; i++) {
                if (a[i] != that.a[i]) return false;
            }
            return true;
        }

        @Override public int hashCode() {
            return hash;
        }
    }
}

另存为HankelCombinatorics.java,编译为javac HankelCombinatorics.java,运行为java -Xmx2G HankelCombinatorics

随着NUM_THREADS = 4我的四核机器上它得到20420819767436n=8在50至55秒钟以后,运行之间的变化相当数量的; 我希望它可以轻松地在八核计算机上管理相同的内容,但是要花一个小时或更长时间才能获得n=9

怎么运行的

给定n,存在2^(2n-1)二进制nx nHankel矩阵。行可以按n!方式排列,列可以按方式排列n!。我们要做的就是避免重复计算...

如果您计算每行的总和,那么对行进行排列或对列进行排列都不会更改总和的多集。例如

0 1 1 0 1
1 1 0 1 0
1 0 1 0 0
0 1 0 0 1
1 0 0 1 0

具有行和multiset {3, 3, 2, 2, 2},从中得出的所有Hankelable矩阵也是如此。这意味着我们可以通过这些行和多集对Hankel矩阵进行分组,然后利用多个处理器核心独立处理每个组。

还有一个可利用的对称性:零多于零的矩阵与具有多于零的矩阵是双射的。

当汉克尔矩阵发生重复计算M_1与行置换r_1和列置换c_1汉克尔矩阵匹配M_2与行置换r_2和列置换c_2(最多两个而不是三个的M_1 = M_2r_1 = r_2c_1 = c_2)。该行和列的排列是独立的,所以如果我们应用行排列r_1M_1和行置换r_2M_2,列如多集必须相等。因此,对于每个组,我计算通过将行置换应用于组中的矩阵而获得的所有列多集。获得多集规范表示的简单方法是对列进行排序,这在下一步中也很有用。

获得了不同的列多集后,我们需要找到n!每个列的多少个排列是唯一的。在这一点上,只有在给定的列多集具有重复的列的情况下,才可以进行重复计数:我们需要做的是计算多集中每个不同列的出现次数,然后计算相应的多项式系数。由于列已排序,因此计数很容易。

最后,我们将它们全部加起来。

渐近复杂度要达到完全精确的计算并非易事,因为我们需要对集合进行一些假设。我们根据2^(2n-2) n!列多集的顺序进行评估n^2 ln n,每次花费时间(包括排序);如果分组只花一个ln n因素,那么我们就有时间复杂性Theta(4^n n! n^2 ln n)。但是由于指数因子完全控制多项式,所以是Theta(4^n n!) = Theta((4n/e)^n)


这是非常令人印象深刻的。您能谈谈您使用的算法吗?

3

Python2 / 3

很幼稚的方法,用一种缓慢的语言:

import itertools

def permute_rows(m):
    for perm in itertools.permutations(m):
        yield perm

def permute_columns(m):
    T = zip(*m)
    for perm in itertools.permutations(T):
        yield zip(*perm)

N = 1
while True:
    base_template = ["abcdefghijklmnopqrstuvwxyz"[i:i+N] for i in range(N)]

    templates = set()
    for c in permute_rows(base_template):
        for m in permute_columns(c):
            templates.add("".join("".join(row) for row in m))

    def possibs(free, templates):
        if free == 2*N - 1:
            return set(int(t, 2) for t in templates)

        s = set()
        for b in "01":
            new_templates = set(t.replace("abcdefghijklmnopqrstuvwxyz"[free], b) for t in templates)
            s |= possibs(free + 1, new_templates)

        return s

    print(len(possibs(0, templates)))
    N += 1

通过键入运行python script.py


您列出的语言为Python 2/3,但要使其在Python 2中运行,就不需要from __future__ import print_function(或类似的东西)吗?
Alex A.

2
@AlexA。通常,是的,但在这种情况下不是。输入时请考虑Python2的行为return(1)。现在替换returnprint:)
orlp

凉!我每天都学到新东西。:)
Alex A.

2

哈斯克尔

import Data.List
import Data.Hashable
import Control.Parallel.Strategies
import Control.Parallel
import qualified Data.HashSet as S

main = mapM putStrLn $ map (show.countHankellable) [1..]

a§b=[a!!i|i<-b]

hashNub :: (Hashable a, Eq a) => [a] -> [a]
hashNub l = go S.empty l
    where
      go _ []     = []
      go s (x:xs) = if x `S.member` s then go s xs
                                    else x : go (S.insert x s) xs

pmap = parMap rseq

makeMatrix :: Int->[Bool]->[[Bool]]
makeMatrix n vars = [vars§[i..i+n-1]|i<-[0..n-1]]

countHankellable :: Int -> Int
countHankellable n = let
    s = permutations [0..n-1]
    conjugates m = concat[permutations[r§q|r<-m]|q<-s]
    variableSets = sequence [[True,False]|x<-[0..2*(n-1)]]
 in
    length.hashNub.concat.pmap (conjugates.makeMatrix n ) $ variableSets

速度远不及Peter的速度-他到达那里的设置令人印象深刻!现在有了更多从互联网复制的代码。用法:

$ ghc -threaded hankell.hs
$ ./hankell

始终欢迎Haskell回答。谢谢。

@Lembik-我的机器运行状况如何?
亚历山大·布雷特
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.