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;
}
}
}
Simpan sebagai HankelCombinatorics.java
, kompilasi sebagai javac HankelCombinatorics.java
, jalankan sebagai java -Xmx2G HankelCombinatorics
.
Dengan NUM_THREADS = 4
mesin quad-core saya mendapat 20420819767436
untuk n=8
di 50 sampai dengan 55 detik berlalu, dengan jumlah wajar variabilitas antara berjalan; Saya berharap bahwa itu harus dengan mudah mengelola hal yang sama pada mesin octa-core Anda tetapi akan memakan waktu satu jam atau lebih untuk mendapatkannya n=9
.
Bagaimana itu bekerja
Mengingat n
, ada matriks 2^(2n-1)
biner n
x n
Hankel. Baris dapat di permutasi dengan n!
cara, dan kolom dengan n!
cara. Yang perlu kita lakukan adalah menghindari penghitungan ganda ...
Jika Anda menghitung jumlah setiap baris, maka permutasi baris atau permutasi kolom tidak mengubah multiset jumlah. Misalnya
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
memiliki jumlah baris multiset {3, 3, 2, 2, 2}
, dan demikian pula semua matriks Hankelable berasal darinya. Ini berarti bahwa kita dapat mengelompokkan matriks Hankel dengan multiset jumlah baris ini dan kemudian menangani setiap grup secara independen, mengeksploitasi beberapa inti prosesor.
Ada juga simetri yang dapat dieksploitasi: matriks dengan nol lebih banyak daripada yang ada di bijih dengan matriks dengan lebih banyak dari nol.
Penghitungan ganda terjadi ketika Hankel matriks M_1
dengan baris permutasi r_1
dan kolom permutasi c_1
cocok Hankel matriks M_2
dengan baris permutasi r_2
dan kolom permutasi c_2
(sampai dengan dua tapi tidak semua tiga M_1 = M_2
, r_1 = r_2
, c_1 = c_2
). Baris dan kolom permutasi independen, jadi jika kita menerapkan baris permutasi r_1
untuk M_1
dan baris permutasi r_2
untuk M_2
, kolom sebagai multisets harus sama. Jadi untuk setiap grup, saya menghitung semua multiset kolom yang diperoleh dengan menerapkan permutasi baris ke matriks dalam grup. Cara mudah untuk mendapatkan representasi kanonik dari multiset adalah dengan menyortir kolom, dan itu juga berguna pada langkah berikutnya.
Setelah memperoleh multiset kolom yang berbeda, kita perlu menemukan berapa banyak n!
permutasi dari masing-masing yang unik. Pada titik ini, penghitungan ganda hanya dapat terjadi jika multiset kolom yang diberikan memiliki kolom duplikat: apa yang perlu kita lakukan adalah menghitung jumlah kemunculan setiap kolom yang berbeda dalam multiset dan kemudian menghitung koefisien multinomial yang sesuai. Karena kolom diurutkan, mudah untuk menghitung.
Akhirnya kami menambahkan semuanya.
Kompleksitas asimptotik bukanlah hal sepele untuk dihitung dengan presisi penuh, karena kita perlu membuat beberapa asumsi tentang perangkat. Kami mengevaluasi urutan 2^(2n-2) n!
multiset kolom, meluangkan n^2 ln n
waktu untuk masing-masing (termasuk penyortiran); jika pengelompokan tidak mengambil lebih dari satu ln n
faktor, kami memiliki kompleksitas waktu Theta(4^n n! n^2 ln n)
. Tetapi karena faktor eksponensial sepenuhnya mendominasi faktor polinomial, maka faktor itu Theta(4^n n!) = Theta((4n/e)^n)
.