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;
}
}
}
Enregistrer sous HankelCombinatorics.java
, compiler sous javac HankelCombinatorics.java
, exécuter sous java -Xmx2G HankelCombinatorics
.
Avec NUM_THREADS = 4
ma machine quad-core , il obtient 20420819767436
pour n=8
dans 50 à 55 secondes se sont écoulées, avec une quantité de juste de la variabilité entre les pistes; J'espère qu'il devrait facilement gérer la même chose sur votre machine octa-core, mais cela prendra une heure ou plus pour l'obtenir n=9
.
Comment ça fonctionne
Étant donné n
, il existe des matrices 2^(2n-1)
binaires n
x n
Hankel. Les lignes peuvent être permutées de n!
différentes manières et les colonnes de n!
différentes manières. Tout ce que nous devons faire est d'éviter le double comptage ...
Si vous calculez la somme de chaque ligne, alors ni permuter les lignes ni permuter les colonnes ne modifie le multi-ensemble de sommes. Par exemple
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
possède un multi-ensemble de somme de lignes {3, 3, 2, 2, 2}
, tout comme toutes les matrices Hankelable qui en dérivent. Cela signifie que nous pouvons regrouper les matrices Hankel par ces multisets de somme de lignes, puis gérer chaque groupe indépendamment, en exploitant plusieurs cœurs de processeur.
Il y a aussi une symétrie exploitable: les matrices avec plus de zéros que de uns sont en bijection avec les matrices avec plus de zéros.
Double comptage se produit lorsque la matrice Hankel M_1
avec permutation de ligne r_1
et permutation de colonne c_1
correspond à la matrice Hankel M_2
avec permutation de ligne r_2
et permutation de colonne c_2
(avec un maximum de deux , mais pas tous les trois M_1 = M_2
, r_1 = r_2
, c_1 = c_2
). Les permutations de ligne et de colonne sont indépendantes, donc si nous appliquons la permutation de ligne r_1
à M_1
et la permutation de ligne r_2
à M_2
, les colonnes en tant que multisets doivent être égales. Donc, pour chaque groupe, je calcule tous les multisets de colonne obtenus en appliquant une permutation de ligne à une matrice du groupe. Le moyen facile d'obtenir une représentation canonique des multisets est de trier les colonnes, ce qui est également utile à l'étape suivante.
Après avoir obtenu les multisets de colonnes distinctes, nous devons trouver combien de n!
permutations de chacune sont uniques. À ce stade, le double comptage ne peut se produire que si un multiset de colonne donné a des colonnes en double: ce que nous devons faire est de compter le nombre d'occurrences de chaque colonne distincte dans le multiset, puis de calculer le coefficient multinomial correspondant. Comme les colonnes sont triées, il est facile de faire le décompte.
Enfin, nous les additionnons tous.
La complexité asymptotique n'est pas triviale à calculer avec une précision totale, car nous devons faire quelques hypothèses sur les ensembles. Nous évaluons sur l'ordre des 2^(2n-2) n!
multisets de colonne, en prenant du n^2 ln n
temps pour chacun (y compris le tri); si le regroupement ne prend pas plus d'un ln n
facteur, nous avons une complexité temporelle Theta(4^n n! n^2 ln n)
. Mais puisque les facteurs exponentiels dominent complètement les polynômes, c'est le cas Theta(4^n n!) = Theta((4n/e)^n)
.