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;
}
}
}
Lưu thành HankelCombinatorics.java
, biên dịch thành javac HankelCombinatorics.java
, chạy như java -Xmx2G HankelCombinatorics
.
Với NUM_THREADS = 4
máy lõi tứ của tôi, nó được20420819767436
cho n=8
vào từ 50 đến 55 giây trôi qua, với một số lượng hợp lý của sự biến đổi giữa chạy; Tôi hy vọng rằng nó sẽ dễ dàng quản lý tương tự trên máy octa-core của bạn nhưng sẽ mất một giờ hoặc hơn để có được n=9
.
Làm thế nào nó hoạt động
Cho n
, có ma trận x Hankel 2^(2n-1)
nhị phân . Các hàng có thể được hoán vị trongn
n
n!
cách và các cột theo n!
cách. Tất cả những gì chúng ta cần làm là tránh tính hai lần ...
Nếu bạn tính tổng của mỗi hàng, thì việc hoán vị các hàng cũng không cho phép các cột sẽ thay đổi nhiều tổng. Ví dụ
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
có nhiều hàng tổng {3, 3, 2, 2, 2}
, và tất cả các ma trận Hankelable có nguồn gốc từ nó. Điều này có nghĩa là chúng ta có thể nhóm các ma trận Hankel theo các đa tổng hàng này và sau đó xử lý từng nhóm một cách độc lập, khai thác nhiều lõi xử lý.
Ngoài ra còn có một đối xứng có thể khai thác: các ma trận có nhiều số 0 hơn so với các ma trận với các ma trận có nhiều số hơn các số không.
Double-tính toán xảy ra khi Hankel ma trận M_1
với hoán vị hàng r_1
và cột hoán vị c_1
phù hợp với ma trận Hankel M_2
với hoán vị hàng r_2
và cột hoán vị c_2
(lên đến hai nhưng không phải tất cả ba M_1 = M_2
, r_1 = r_2
, c_1 = c_2
). Các hoán vị hàng và cột là độc lập, vì vậy nếu chúng ta áp dụng hoán vị hàng r_1
cho M_1
và hoán vị hàng r_2
cho M_2
, các cột dưới dạng nhiều trang phải bằng nhau. Vì vậy, đối với mỗi nhóm, tôi tính toán tất cả các đa cột có được bằng cách áp dụng hoán vị hàng cho một ma trận trong nhóm. Cách dễ dàng để có được một biểu diễn chính tắc của nhiều trang là sắp xếp các cột và điều đó cũng hữu ích trong bước tiếp theo.
Có được nhiều cột khác nhau, chúng ta cần tìm xem có bao nhiêu n!
hoán vị của mỗi cột là duy nhất. Tại thời điểm này, việc đếm kép chỉ có thể xảy ra nếu một multiset cột đã cho có các cột trùng lặp: điều chúng ta cần làm là đếm số lần xuất hiện của mỗi cột riêng biệt trong multiset và sau đó tính hệ số đa cực tương ứng. Vì các cột được sắp xếp, thật dễ dàng để đếm.
Cuối cùng, chúng tôi thêm tất cả chúng lên.
Độ phức tạp tiệm cận không phải là nhỏ để tính chính xác hoàn toàn, bởi vì chúng ta cần đưa ra một số giả định về các bộ. Chúng tôi đánh giá theo thứ tự của nhiều 2^(2n-2) n!
cột, dành n^2 ln n
thời gian cho từng cột (bao gồm cả việc sắp xếp); nếu việc phân nhóm không chiếm nhiều hơn một ln n
yếu tố, chúng ta có sự phức tạp về thời gian Theta(4^n n! n^2 ln n)
. Nhưng vì các yếu tố theo cấp số nhân hoàn toàn thống trị các đa thức, nên nó Theta(4^n n!) = Theta((4n/e)^n)
.