Знайдіть найвищу матрицю оцінок без властивості X


14

Ця проблема є частково викликом алгоритмів, частково завданням оптимізації, а частково просто викликом найшвидшого коду.

Циклічна матриця повністю задається її першим рядком r. Решта рядків - це кожен циклічний перестановок рядка rзі зміщенням, рівним індексу рядків. Ми дозволимо циклічні матриці, які не є квадратними, щоб вони просто пропустили кілька останніх рядків. Однак ми завжди припускаємо, що кількість рядків не більше кількості стовпців. Наприклад, розглянемо наступну циклічну матрицю 3 на 5.

10111
11011
11101

Ми говоримо, що матриця має властивість X, якщо вона містить два непусті набори стовпців з невідповідними індексами, які мають однакову (векторну) суму. Векторна сума двох стовпців - це просто елементне підсумовування двох стовпців. Це сума двох стовпців, що містять xелементи, кожен - це інший стовпець, що містить xелементи.

Матриця вище тривіально має властивість X, оскільки перший і останній стовпці однакові. Матриця ідентичності ніколи не має властивості X.

Якщо ми просто видалимо останній стовпчик матриці вище, то отримаємо приклад, який не має властивості X і дав би оцінку 4/3.

1011
1101
1110

Завдання

Завдання полягає в тому, щоб написати код, щоб знайти найвищий циклічний матричний бал, записи якого всі 0 або 1 і який не має властивості X.

Оцінка

Ваш результат буде стовпчиком з числом, розділеним на кількість рядків у вашій найкращій матриці оцінок.

Tie Breaker

Якщо дві відповіді мають однаковий бал, той, хто подав перший, виграє.

У (дуже) малоймовірному випадку, коли хтось знайде спосіб отримати необмежену кількість балів, буде прийнято перше дійсне підтвердження такого рішення. У ще більш малоймовірному випадку, коли ви зможете знайти доказ оптимальності кінцевої матриці, я, звичайно, також присуджую виграш.

Підказка

Отримати оцінку 12/8 не надто складно.

Мови та бібліотеки

Ви можете використовувати будь-яку мову, у якій є доступний компілятор / перекладач / тощо. для Linux та будь-яких бібліотек, які також вільно доступні для Linux.

Провідні записи

  • 36/19 Пітер Тейлор (Java)
  • 32/17 Субоптимус Прайм (C #)
  • 21/12 від Justhalf (Python 2)

Ах, властивість X знаходиться на стовпцях, а не у рядках.
Оптимізатор

Як було написано, матриця 1 на 2 01 має властивість X, оскільки множина першого стовпця має ту саму векторну суму, що і порожня множина. Можливо, ви мали на увазі непорожні набори стовпців? Я думаю, що чистіше, але не змінювати це.
xnor

2
Найпростішим читанням правил все ж 01є властивість X : (1) = (0) + (1). Якщо ви хочете виключити це, то вам слід сказати, що два набори стовпців повинні бути роз'єднаними.
Пітер Тейлор

1
Це питання дасть багато розуміння цієї проблеми (про те, як важко перевірити властивість X, що є NP-жорстким, на жаль) mathoverflow.net/questions/157634/…
justhalf

3
Наразі ми просто змушуємо всі 2^mкомбінації стовпців перевіряти властивість X. Якби ми могли якось розробити схему "зустрітися в середині" (див. Проблему "сума підмножини"), це, ймовірно, може зменшити це до m * 2^(m/2)...
kennytm

Відповіді:


11

16/9 20/11 22/12 28/15 30/16 32/17 34/18 36/19 (Java)

Для цього використовується ряд ідей, щоб зменшити пошуковий простір та вартість. Перегляньте історію версій, щоб отримати докладнішу інформацію про попередні версії коду.

  • Зрозуміло, що wlog ми можемо розглядати лише матриці циркуляції, у яких перший рядок є a ліндонським словом : якщо слово не є простим, воно повинно мати властивість X, інакше ми можемо обертатись, не впливаючи на рахунок чи властивість X.
  • На основі евристики спостережуваних коротких переможців я зараз перебираю слова Ліндона, починаючи з тих, що мають 50% щільність (тобто стільки ж 0і 1), і працюю; Я використовую алгоритм, описаний у коді Сірого, для намиста з фіксованою щільністю та слів Ліндона в постійний амортизований час , Савада та Вільямс, Теоретична інформатика 502 (2013): 46-54.
  • Емпіричне спостереження полягає в тому, що значення трапляються парами: кожне оптимальне слово Ліндона, яке я знайшов, набирає такої ж кількості, як і його зворот. Тож я отримую приблизно коефіцієнт двох прискорень, розглядаючи лише одну половину кожної такої пари.
  • Мій оригінальний код працював, BigIntegerщоб дати точний тест. Я отримую значне покращення швидкості, ризикуючи помилковими негативами, керуючи модулем великим рівнем і зберігаючи все в примітивах. Я вибрав прем'єр - найбільший розмір, менший за 2 57 , оскільки це дозволяє множити на базу мого уявного векторного подання без переповнення.
  • Я вкрав евристику Suboptimus Prime , що можна отримати швидкі відхилення, розглядаючи підмножини в порядку збільшення розміру. Зараз я об'єднав цю ідею з потрійним підмножиною "зустріч-в-середині" для тесту на збір підмножини. ( Подяка KennyTM за те, що він запропонував спробувати адаптувати підхід до цілої задачі підмножини; я думаю, що xnor і бачили спосіб зробити це майже одночасно). Замість того, щоб шукати два підмножини, які можуть включати кожен стовпчик 0 або 1 раз і мати однакову суму, ми шукаємо одну підмножину, яка може включати кожен стовпець -1, 0 або 1 раз і сумувати до нуля. Це значно знижує потреби в пам'яті.
  • Є додатковий фактор двох економії вимог до пам’яті, враховуючи, що оскільки у кожного елемента в ньому {-1,0,1}^mє заперечення, {-1,0,1}^mто для зберігання одного з двох необхідно лише.
  • Я також покращую вимоги до пам'яті та продуктивність за допомогою користувацької реалізації хешмапу. Для тестування 36/19 потрібно зберігати 3 ^ 18 сум, а 3 ^ 18 довгих майже 3 Гб, без накладних витрат - я дав йому 6 ГБ купи, оскільки 4 ГБ було недостатньо; щоб продовжувати роботу (тобто тест 38/20) в межах 8 ГБ оперативної пам’яті, знадобиться додаткова оптимізація для зберігання вставки, а не довга. З 20 біт, необхідних для того, щоб сказати, який підмножина виробляє суму, яка залишила б 12 біт плюс неявні біти з відра; Я боюся, що не було б занадто багато помилкових зіткнень, щоб отримати будь-які хіти.
  • Оскільки маса доказів говорить про те, що ми повинні розглядати 2n/(n+1), я прискорюю роботу, просто перевіряючи це.
  • Існує деякий непотрібний, але заспокійливий статистичний результат.
import java.util.*;

// Aiming to find a solution for (2n, n+1).
public class PPCG41021_QRTernary_FixedDensity {
    private static final int N = 36;
    private static int density;
    private static long start;
    private static long nextProgressReport;

    public static void main(String[] args) {
        start = System.nanoTime();
        nextProgressReport = start + 5 * 60 * 1000000000L;

        // 0, -1, 1, -2, 2, ...
        for (int i = 0; i < N - 1; i++) {
            int off = i >> 1;
            if ((i & 1) == 1) off = ~off;
            density = (N >> 1) + off;

            // Iterate over Lyndon words of length N and given density.
            for (int j = 0; j < N; j++) a[j] = j < N - density ? '0' : '1';
            c = 1;
            Bs[1] = N - density;
            Bt[1] = density;
            gen(N - density, density, 1);
            System.out.println("----");
        }

        System.out.println("Finished in " + (System.nanoTime() - start)/1000000 + " ms");
    }

    private static int c;
    private static int[] Bs = new int[N + 1], Bt = new int[N + 1];
    private static char[] a = new char[N];
    private static void gen(int s, int t, int r) {
        if (s > 0 && t > 0) {
            int j = oracle(s, t, r);
            for (int i = t - 1; i >= j; i--) {
                updateBlock(s, t, i);
                char tmp = a[s - 1]; a[s - 1] = a[s+t-i - 1]; a[s+t-i - 1] = tmp;
                gen(s-1, t-i, testSuffix(r) ? c-1 : r);
                tmp = a[s - 1]; a[s - 1] = a[s+t-i - 1]; a[s+t-i - 1] = tmp;
                restoreBlock(s, t, i);
            }
        }
        visit();
    }

    private static int oracle(int s, int t, int r) {
        int j = pseudoOracle(s, t, r);
        updateBlock(s, t, j);
        int p = testNecklace(testSuffix(r) ? c - 1 : r);
        restoreBlock(s, t, j);
        return p == N ? j : j + 1;
    }

    private static int pseudoOracle(int s, int t, int r) {
        if (s == 1) return t;
        if (c == 1) return s == 2 ? N / 2 : 1;
        if (s - 1 > Bs[r] + 1) return 0;
        if (s - 1 == Bs[r] + 1) return cmpPair(s-1, t, Bs[c-1]+1, Bt[c-1]) <= 0 ? 0 : 1;
        if (s - 1 == Bs[r]) {
            if (s == 2) return Math.max(t - Bt[r], (t+1) >> 1);
            return Math.max(t - Bt[r], (cmpPair(s-1, t, Bs[c-1] + 1, Bt[c-1]) <= 0) ? 0 : 1); 
        }
        if (s == Bs[r]) return t;
        throw new UnsupportedOperationException("Hit the case not covered by the paper or its accompanying code");
    }

    private static int testNecklace(int r) {
        if (density == 0 || density == N) return 1;
        int p = 0;
        for (int i = 0; i < c; i++) {
            if (r - i <= 0) r += c;
            if (cmpBlocks(c-i, r-i) < 0) return 0;
            if (cmpBlocks(c-i, r-1) > 0) return N;
            if (r < c) p += Bs[r-i] + Bt[r-i];
        }
        return p;
    }

    private static int cmpPair(int a1, int a2, int b1, int b2) {
        if (a1 < b1) return -1;
        if (a1 > b1) return 1;
        if (a2 < b2) return -1;
        if (a2 > b2) return 1;
        return 0;
    }

    private static int cmpBlocks(int i, int j) {
        return cmpPair(Bs[i], Bt[i], Bs[j], Bt[j]);
    }

    private static boolean testSuffix(int r) {
        for (int i = 0; i < r; i++) {
            if (c - 1 - i == r) return true;
            if (cmpBlocks(c-1-i, r-i) < 0) return false;
            if (cmpBlocks(c-1-i, r-1) > 0) return true;
        }
        return false;
    }

    private static void updateBlock(int s, int t, int i) {
        if (i == 0 && c > 1) {
            Bs[c-1]++;
            Bs[c] = s - 1;
        }
        else {
            Bs[c] = 1;
            Bt[c] = i;
            Bs[c+1] = s-1;
            Bt[c+1] = t-i;
            c++;
        }
    }

    private static void restoreBlock(int s, int t, int i) {
        if (i == 0 && (c > 0 || (Bs[1] != 1 || Bt[1] != 0))) {
            Bs[c-1]--;
            Bs[c] = s;
        }
        else {
            Bs[c-1] = s;
            Bt[c-1] = t;
            c--;
        }
    }

    private static long[] stats = new long[N/2+1];
    private static long visited = 0;
    private static void visit() {
        String word = new String(a);

        visited++;
        if (precedesReversal(word) && testTernary(word)) System.out.println(word + " after " + (System.nanoTime() - start)/1000000 + " ms");
        if (System.nanoTime() > nextProgressReport) {
            System.out.println("Progress: visited " + visited + "; stats " + Arrays.toString(stats) + " after " + (System.nanoTime() - start)/1000000 + " ms");
             nextProgressReport += 5 * 60 * 1000000000L;
        }
    }

    private static boolean precedesReversal(String w) {
        int n = w.length();
        StringBuilder rev = new StringBuilder(w);
        rev.reverse();
        rev.append(rev, 0, n);
        for (int i = 0; i < n; i++) {
            if (rev.substring(i, i + n).compareTo(w) < 0) return false;
        }
        return true;
    }

    private static boolean testTernary(String word) {
        int n = word.length();
        String rep = word + word;

        int base = 1;
        for (char ch : word.toCharArray()) base += ch & 1;

        // Operating base b for b up to 32 implies that we can multiply by b modulo p<2^57 without overflowing a long.
        // We're storing 3^(n/2) ~= 2^(0.8*n) sums, so while n < 35.6 we don't get *too* bad a probability of false reject.
        // (In fact the birthday paradox assumes independence, and our values aren't independent, so we're better off than that).
        long p = (1L << 57) - 13;
        long[] basis = new long[n];
        basis[0] = 1;
        for (int i = 1; i < basis.length; i++) basis[i] = (basis[i-1] * base) % p;

        int rows = n / 2 + 1;
        long[] colVals = new long[n];
        for (int col = 0; col < n; col++) {
            for (int row = 0; row < rows; row++) {
                colVals[col] = (colVals[col] + basis[row] * (rep.charAt(row + col) & 1)) % p;
            }
        }

        MapInt57Int27 map = new MapInt57Int27();
        // Special-case the initial insertion.
        int[] oldLens = new int[map.entries.length];
        int[] oldSupercounts = new int[1 << 10];
        {
            // count = 1
            for (int k = 0; k < n/2; k++) {
                int val = 1 << (25 - k);
                if (!map.put(colVals[k], val)) { stats[1]++; return false; }
                if (!map.put(colVals[k + n/2], val + (1 << 26))) { stats[1]++; return false; }
            }
        }
        final long keyMask = (1L << 37) - 1;
        for (int count = 2; count <= n/2; count++) {
            int[] lens = map.counts.clone();
            int[] supercounts = map.supercounts.clone();
            for (int sup = 0; sup < 1 << 10; sup++) {
                int unaccountedFor = supercounts[sup] - oldSupercounts[sup];
                for (int supi = 0; supi < 1 << 10 && unaccountedFor > 0; supi++) {
                    int i = (sup << 10) + supi;
                    int stop = lens[i];
                    unaccountedFor -= stop - oldLens[i];
                    for (int j = oldLens[i]; j < stop; j++) {
                        long existingKV = map.entries[i][j];
                        long existingKey = ((existingKV & keyMask) << 20) + i;
                        int existingVal = (int)(existingKV >>> 37);

                        // For each possible prepend...
                        int half = (existingVal >> 26) * n/2;
                        // We have 27 bits of key, of which the top marks the half, so 26 bits. That means there are 6 bits at the top which we need to not count.
                        int k = Integer.numberOfLeadingZeros(existingVal << 6) - 1;
                        while (k >= 0) {
                            int newVal = existingVal | (1 << (25 - k));
                            long pos = (existingKey + colVals[k + half]) % p;
                            if (pos << 1 > p) pos = p - pos;
                            if (pos == 0 || !map.put(pos, newVal)) { stats[count]++; return false; }
                            long neg = (p - existingKey + colVals[k + half]) % p;
                            if (neg << 1 > p) neg = p - neg;
                            if (neg == 0 || !map.put(neg, newVal)) { stats[count]++; return false; }
                            k--;
                        }
                    }
                }
            }
            oldLens = lens;
            oldSupercounts = supercounts;
        }

        stats[n/2]++;
        return true;
    }

    static class MapInt57Int27 {
        private long[][] entries;
        private int[] counts;
        private int[] supercounts;

        public MapInt57Int27() {
            entries = new long[1 << 20][];
            counts = new int[1 << 20];
            supercounts = new int[1 << 10];
        }

        public boolean put(long key, int val) {
            int bucket = (int)(key & (entries.length - 1));
            long insert = (key >>> 20) | (((long)val) << 37);
            final long mask = (1L << 37) - 1;

            long[] chain = entries[bucket];
            if (chain == null) {
                chain = new long[16];
                entries[bucket] = chain;
                chain[0] = insert;
                counts[bucket]++;
                supercounts[bucket >> 10]++;
                return true;
            }

            int stop = counts[bucket];
            for (int i = 0; i < stop; i++) {
                if ((chain[i] & mask) == (insert & mask)) {
                    return false;
                }
            }

            if (stop == chain.length) {
                long[] newChain = new long[chain.length < 512 ? chain.length << 1 : chain.length + 512];
                System.arraycopy(chain, 0, newChain, 0, chain.length);
                entries[bucket] = newChain;
                chain = newChain;
            }
            chain[stop] = insert;
            counts[bucket]++;
            supercounts[bucket >> 10]++;
            return true;
        }
    }
}

Перший знайдений - це

000001001010110001000101001111111111

і це єдиний удар за 15 годин.

Менші переможці:

4/3:    0111                       (plus 8 different 8/6)
9/6:    001001011                  (and 5 others)
11/7:   00010100111                (and 3 others)
13/8:   0001001101011              (and 5 others)
15/9:   000010110110111            (and 21 others)
16/9:   0000101110111011           (and 1 other)
20/11:  00000101111011110111       (and others)
22/12:  0000001100110011101011     (and others)
24/13:  000000101011101011101011   (and others)
26/14:  00000001101110010011010111 (and others)
28/15:  0000000010000111100111010111 (and others)
30/16:  000000001011001110011010101111 (and probably others)
32/17:  00001100010010100100101011111111 (and others)
34/18:  0000101000100101000110010111111111 (and others)

Це хороше вдосконалення. Здається, використання ліндонських слів означає, що вам потрібно перевірити лише 2 ^ n / n двійкових рядків для першого ряду, а не 2 ^ n.

Оскільки ви використовуєте кожну цифру BigInteger як матричну комірку, чи не буде помилкової відповіді, коли n> 10?
kennytm

@KennyTM, зауважте, що другим параметром є радіус. Є невелика помилка: я повинен використовувати, nа не rows, хоча це безпечно, оскільки він би відкинув дійсні рішення, а не прийняв недійсні. Це також не впливає на результати.
Пітер Тейлор

1
Я думаю, що ми практично обмежені цим показником, оскільки перевірка властивості X займає дуже багато часу, якщо ми не знайшли іншу еквівалентну умову, яку можна оцінити швидше. Ось чому я так нетерпляче бачив, що "non-prime" передбачає властивість X = D
justhalf

1
@SuboptimusPrime, я знайшов його на people.math.sfu.ca/~kya17/teaching/math343/16-343.pdf та виправив помилку. Цікаво, що алгоритм, який я зараз використовую для повторення слів Ліндона, є одним із класів пов'язаних алгоритмів, який також робить підмножини k-n-n, тому я міг би переробити і поділити деякий код.
Пітер Тейлор

9

Пітон 2 - 21/12

У процесі доведення, що 2-(3/n)завжди існує для будь-якогоn

Надихнувшись цим питанням , я використав De Bruijn Sequence для грубої сили можливих матриць. І після грубої сили n=6,7,8,9,10я знайшов зразок, який найвищим рішенням завжди є у формі (n, 2n-3).

Тож я створив інший метод, щоб зв'язати саме з такою формою матриці, і використовувати багатопроцесорний процес, щоб прискорити роботу, оскільки це завдання широко розповсюджується. У 16-ядерному ubuntu він може знайти рішення n=12приблизно за 4 хвилини:

Пробуючи (0, 254)
Пробуючи (254, 509)
Пробуючи (509, 764)
Пробуючи (764, 1018)
Пробуючи (1018, 1273)
Пробуючи (1273, 1528)
Пробуючи (1528, 1782)
Пробуючи (1782, 2037)
Пробуючи (2037, 2292)
Пробуючи (2292, 2546)
Пробуючи (2546, 2801)
Пробуючи (2801, 3056)
Пробуючи (3056, 3310)
Пробуючи (3820, 4075)
Пробуючи (3565, 3820)
Пробуючи (3310, 3565)
(1625, 1646)
[[0 0 0 1 0 0 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0]
 [0 0 1 0 0 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 0]
 [0 1 0 0 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 0 0]
 [1 0 0 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 0 0 0]
 [0 0 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1]
 [0 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1 0]
 [1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1 0 0]
 [0 1 1 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1 0 0 1]
 [1 1 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1 0 0 1 0]
 [1 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1 0 0 1 0 1]
 [1 1 0 0 0 1 0 0 1 1 0 0 0 0 1 0 0 1 0 1 1]
 [1 0 0 0 1 0 0 1 1 0 0 0 0 1 0 0 1 0 1 1 1]]
(12, 21)
Оцінка: 1,7500

справжні 4м9.121с
користувач 42m47.472s
sys 0m5.780s

Основна частина обчислень йде на перевірку властивості X, яка вимагає перевірити всі підмножини (є 2^(2n-3)підмножини)

Зауважте, що я обертаю перший рядок вліво, а не вправо, як у питанні. Але вони рівноцінні, оскільки можна просто перевернути всю матрицю. =)

Кодекс:

import math
import numpy as np
from itertools import combinations
from multiprocessing import Process, Queue, cpu_count

def de_bruijn(k, n):
    """
    De Bruijn sequence for alphabet k
    and subsequences of length n.
    """
    alphabet = list(range(k))
    a = [0] * k * n
    sequence = []
    def db(t, p):
        if t > n:
            if n % p == 0:
                for j in range(1, p + 1):
                    sequence.append(a[j])
        else:
            a[t] = a[t - p]
            db(t + 1, p)
            for j in range(a[t - p] + 1, k):
                a[t] = j
                db(t + 1, t)
    db(1, 1)
    return sequence

def generate_cyclic_matrix(seq, n):
    result = []
    for i in range(n):
        result.append(seq[i:]+seq[:i])
    return np.array(result)

def generate_cyclic_matrix_without_property_x(n=3, n_jobs=-1):
    seq = de_bruijn(2,n)
    seq = seq + seq[:n/2]
    max_idx = len(seq)
    max_score = 1
    max_matrix = np.array([[]])
    max_ij = (0,0)
    workers = []
    queue = Queue()
    if n_jobs < 0:
        n_jobs += cpu_count()+1
    for i in range(n_jobs):
        worker = Process(target=worker_function, args=(seq,i*(2**n-2*n+3)/n_jobs, (i+1)*(2**n-2*n+3)/n_jobs, n, queue))
        workers.append(worker)
        worker.start()
    (result, max_ij) = queue.get()
    for worker in workers:
        worker.terminate()
    return (result, max_ij)

def worker_function(seq,min_idx,max_idx,n,queue):
    print 'Trying (%d, %d)' % (min_idx, max_idx)
    for i in range(min_idx, max_idx):
        j = i+2*n-3
        result = generate_cyclic_matrix(seq[i:j], n)
        if has_property_x(result):
            continue
        else:
            queue.put( (result, (i,j)) )
            return

def has_property_x(mat):
    vecs = zip(*mat)
    vector_sums = set()
    for i in range(1, len(vecs)+1):
        for combination in combinations(vecs, i):
            vector_sum = tuple(sum(combination, np.array([0]*len(mat))))
            if vector_sum in vector_sums:
                return True
            else:
                vector_sums.add(vector_sum)
    return False

def main():
    import sys
    n = int(sys.argv[1])
    if len(sys.argv) > 2:
        n_jobs = int(sys.argv[2])
    else:
        n_jobs = -1
    (matrix, ij) = generate_cyclic_matrix_without_property_x(n, n_jobs)
    print ij
    print matrix
    print matrix.shape
    print 'Score: %.4f' % (float(matrix.shape[1])/matrix.shape[0])

if __name__ == '__main__':
    main()

Стара відповідь, для довідки

Оптимальне рішення на даний момент ( n=10):

(855, 872)
[[1 1 0 1 0 1 0 0 1 1 1 1 0 1 1 1 0]
 [1 0 1 0 1 0 0 1 1 1 1 0 1 1 1 0 1]
 [0 1 0 1 0 0 1 1 1 1 0 1 1 1 0 1 1]
 [1 0 1 0 0 1 1 1 1 0 1 1 1 0 1 1 0]
 [0 1 0 0 1 1 1 1 0 1 1 1 0 1 1 0 1]
 [1 0 0 1 1 1 1 0 1 1 1 0 1 1 0 1 0]
 [0 0 1 1 1 1 0 1 1 1 0 1 1 0 1 0 1]
 [0 1 1 1 1 0 1 1 1 0 1 1 0 1 0 1 0]
 [1 1 1 1 0 0 1 1 1 0 1 1 0 1 0 1 0 0]
 [1 1 1 0 1 1 1 0 1 1 0 1 0 1 0 0 1]]
(10, 17)
Оцінка: 1,7000

Для n=7:

(86, 97)
[[0 1 1 1 0 1 0 0 1 1 1]
 [1 1 1 0 1 0 0 1 1 1 0]
 [1 1 0 1 0 0 1 1 1 0 1]
 [1 0 1 0 0 1 1 1 0 1 1]
 [0 1 0 0 1 1 1 0 1 1 1]
 [1 0 0 1 1 1 0 1 1 1 0]
 [0 0 1 1 1 0 1 1 1 0 1]]
(7, 11)
Оцінка: 1,5714

Розчин з формою, описаною в OP ( n=8):

(227, 239)
[[0 1 0 1 1 1 1 1 0 1 1 0]
 [1 0 1 1 1 1 1 0 1 1 0 0]
 [0 1 1 1 1 1 0 1 1 0 0 1]
 [1 1 1 1 1 0 1 1 0 0 1 0]
 [1 1 1 1 0 1 1 0 0 1 0 1]
 [1 1 1 0 1 1 0 0 1 0 1 1]
 [1 1 0 1 1 0 0 1 0 1 1 1]
 [1 0 1 1 0 0 1 0 1 1 1 1]]
(8, 12)
Оцінка: 1,5000

Але кращий ( n=8):

(95, 108)
[[0 1 1 0 0 1 0 0 0 1 1 0 1]
 [1 1 0 0 1 0 0 0 1 1 0 1 0]
 [1 0 0 1 0 0 0 1 1 0 1 0 1]
 [0 0 1 0 0 0 1 1 0 1 0 1 1]
 [0 1 0 0 0 1 1 0 1 0 1 1 0]
 [1 0 0 0 1 1 0 1 0 1 1 0 0]
 [0 0 0 1 1 0 1 0 1 1 0 0 1]
 [0 0 1 1 0 1 0 1 1 0 0 1 0]]
(8, 13)
Оцінка: 1,66250

Він також знайшов ще одне оптимальне рішення n=9:

(103, 118)
[[0 1 0 1 1 1 0 0 0 1 1 0 0 1]
 [1 0 1 1 1 0 0 0 0 1 1 0 0 1 0]
 [0 1 1 1 0 0 0 0 1 1 0 0 1 0 1]
 [1 1 1 0 0 0 0 1 1 0 0 1 0 1 0]
 [1 1 0 0 0 0 1 1 0 0 1 0 1 0 1]
 [1 0 0 0 0 1 1 0 0 1 0 1 0 1 1]
 [0 0 0 0 1 1 0 0 1 0 1 0 1 1 1]
 [0 0 0 1 1 0 0 1 0 1 0 1 1 1 0]
 [0 0 1 1 0 0 1 0 1 0 1 1 1 0 0]]
(9, 15)
Оцінка: 1,6667

Код такий. Це просто груба сила, але принаймні вона може знайти щось краще, ніж претензія ОП =)

import numpy as np
from itertools import combinations

def de_bruijn(k, n):
    """
    De Bruijn sequence for alphabet k
    and subsequences of length n.
    """
    alphabet = list(range(k))
    a = [0] * k * n
    sequence = []
    def db(t, p):
        if t > n:
            if n % p == 0:
                for j in range(1, p + 1):
                    sequence.append(a[j])
        else:
            a[t] = a[t - p]
            db(t + 1, p)
            for j in range(a[t - p] + 1, k):
                a[t] = j
                db(t + 1, t)
    db(1, 1)
    return sequence

def generate_cyclic_matrix(seq, n):
    result = []
    for i in range(n):
        result.append(seq[i:]+seq[:i])
    return np.array(result)

def generate_cyclic_matrix_without_property_x(n=3):
    seq = de_bruijn(2,n)
    max_score = 0
    max_matrix = []
    max_ij = (0,0)
    for i in range(2**n+1):
        for j in range(i+n, 2**n+1):
            score = float(j-i)/n
            if score <= max_score:
                continue
            result = generate_cyclic_matrix(seq[i:j], n)
            if has_property_x(result):
                continue
            else:
                if score > max_score:
                    max_score = score
                    max_matrix = result
                    max_ij = (i,j)
    return (max_matrix, max_ij)

def has_property_x(mat):
    vecs = zip(*mat)
    vector_sums = set()
    for i in range(1, len(vecs)):
        for combination in combinations(vecs, i):
            vector_sum = tuple(sum(combination, np.array([0]*len(mat))))
            if vector_sum in vector_sums:
                return True
            else:
                vector_sums.add(vector_sum)
    return False

def main():
    import sys
    n = int(sys.argv[1])
    (matrix, ij) = generate_cyclic_matrix_without_property_x(n)
    print ij
    print matrix
    print matrix.shape
    print 'Score: %.4f' % (float(matrix.shape[1])/matrix.shape[0])

if __name__ == '__main__':
    main()

Чудовий початок :)

2
@Lembik Тепер я можу перемогти майже (обмежений обчислювальним часом) будь-кого, хто вимагає будь-якого балу нижче 2. =)
justhalf

У такому випадку ви можете перемогти 19/10?

@Lembik Я не думаю, що можу. Це вимагає n >= 31, що означає, що мені потрібно перевірити 2^(2n-3) = 2^59комбінації 31-мірного вектора. Не закінчимо в нашому житті = D
justhalf

2
Чи можете ви довести, що ви завжди можете отримати матрицюn*(2n-3)
xnor

7

24/13 26/14 28/15 30/16 32/17 (C #)

Редагувати: Видалена застаріла інформація з моєї відповіді. Я використовую здебільшого той же алгоритм, що і Пітер Тейлор ( Редагувати: схоже, він зараз використовує кращий алгоритм), хоча я додав деякі власні оптимізації:

  • Я реалізував стратегію "зустрітися в середині" пошуку наборів стовпців з однаковою сумою векторів (запропоновано коментарем KennyTM ). Ця стратегія значно покращила використання пам’яті, але вона досить повільна, тому я додав HasPropertyXFastфункцію, яка швидко перевіряє, чи є невеликий набір з рівними сумами, перш ніж використовувати підхід «зустрітися в середині».
  • Перебираючи набори стовпців у HasPropertyXFast функції, я починаю з перевірки наборів стовпців з 1 стовпцем, потім з 2, 3 тощо. Функція повертається, як тільки знайдено перше зіткнення сум стовпців. На практиці це означає, що мені зазвичай доводиться перевіряти лише кілька сотень чи тисяч наборів стовпців, а не мільйони.
  • Я використовую longзмінні для зберігання та порівняння цілих стовпців та їх векторних сум. Цей підхід принаймні на порядок швидший, ніж порівняння стовпців як масивів.
  • Я додав власну реалізацію хештету, оптимізовану для long типу даних та для моїх моделей використання.
  • Я використовую ті самі 3 хеш-версії протягом усього життя програми, щоб зменшити кількість розподілу пам'яті та підвищити продуктивність.
  • Підтримка багатопотокових передач.

Вихід програми:

00000000000111011101010010011111
10000000000011101110101001001111
11000000000001110111010100100111
11100000000000111011101010010011
11110000000000011101110101001001
11111000000000001110111010100100
01111100000000000111011101010010
00111110000000000011101110101001
10011111000000000001110111010100
01001111100000000000111011101010
00100111110000000000011101110101
10010011111000000000001110111010
01001001111100000000000111011101
10100100111110000000000011101110
01010010011111000000000001110111
10101001001111100000000000111011
11010100100111110000000000011101
Score: 32/17 = 1,88235294117647
Time elapsed: 02:11:05.9791250

Код:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

class Program
{
    const int MaxWidth = 32;
    const int MaxHeight = 17;

    static object _lyndonWordLock = new object();

    static void Main(string[] args)
    {
        Stopwatch sw = Stopwatch.StartNew();
        double maxScore = 0;
        const int minHeight = 17; // 1
        for (int height = minHeight; height <= MaxHeight; height++)
        {
            Console.WriteLine("Row count = " + height);
            Console.WriteLine("Time elapsed: " + sw.Elapsed + "\r\n");

            int minWidth = Math.Max(height, (int)(height * maxScore) + 1);
            for (int width = minWidth; width <= MaxWidth; width++)
            {
#if MULTITHREADING
                int[,] matrix = FindMatrixParallel(width, height);
#else
                int[,] matrix = FindMatrix(width, height);
#endif
                if (matrix != null)
                {
                    PrintMatrix(matrix);
                    Console.WriteLine("Time elapsed: " + sw.Elapsed + "\r\n");
                    maxScore = (double)width / height;
                }
                else
                    break;
            }
        }
    }

#if MULTITHREADING
    static int[,] FindMatrixParallel(int width, int height)
    {
        _lyndonWord = 0;
        _stopSearch = false;

        int threadCount = Environment.ProcessorCount;
        Task<int[,]>[] tasks = new Task<int[,]>[threadCount];
        for (int i = 0; i < threadCount; i++)
            tasks[i] = Task<int[,]>.Run(() => FindMatrix(width, height));

        int index = Task.WaitAny(tasks);
        if (tasks[index].Result != null)
            _stopSearch = true;

        Task.WaitAll(tasks);
        foreach (Task<int[,]> task in tasks)
            if (task.Result != null)
                return task.Result;

        return null;
    }

    static volatile bool _stopSearch;
#endif

    static int[,] FindMatrix(int width, int height)
    {
#if MULTITHREADING
        _columnSums = new LongSet();
        _left = new LongSet();
        _right = new LongSet();
#endif

        foreach (long rowTemplate in GetLyndonWords(width))
        {
            int[,] matrix = new int[width, height];
            for (int x = 0; x < width; x++)
            {
                int cellValue = (int)(rowTemplate >> (width - 1 - x)) % 2;
                for (int y = 0; y < height; y++)
                    matrix[(x + y) % width, y] = cellValue;
            }

            if (!HasPropertyX(matrix))
                return matrix;

#if MULTITHREADING
            if (_stopSearch)
                return null;
#endif
        }

        return null;
    }

#if MULTITHREADING
    static long _lyndonWord;
#endif

    static IEnumerable<long> GetLyndonWords(int length)
    {
        long lyndonWord = 0;
        long max = (1L << (length - 1)) - 1;
        while (lyndonWord <= max)
        {
            if ((lyndonWord % 2 != 0) && PrecedesReversal(lyndonWord, length))
                yield return lyndonWord;

#if MULTITHREADING
            lock (_lyndonWordLock)
            {
                if (_lyndonWord <= max)
                    _lyndonWord = NextLyndonWord(_lyndonWord, length);
                else
                    yield break;

                lyndonWord = _lyndonWord;
            }
#else
            lyndonWord = NextLyndonWord(lyndonWord, length);
#endif
        }
    }

    static readonly int[] _lookup =
    {
        32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4, 7, 17,
        0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5, 20, 8, 19, 18
    };

    static int NumberOfTrailingZeros(uint i)
    {
        return _lookup[(i & -i) % 37];
    }

    static long NextLyndonWord(long w, int length)
    {
        if (w == 0)
            return 1;

        int currentLength = length - NumberOfTrailingZeros((uint)w);
        while (currentLength < length)
        {
            w += w >> currentLength;
            currentLength *= 2;
        }

        w++;

        return w;
    }

    private static bool PrecedesReversal(long lyndonWord, int length)
    {
        int shift = length - 1;

        long reverse = 0;
        for (int i = 0; i < length; i++)
        {
            long bit = (lyndonWord >> i) % 2;
            reverse |= bit << (shift - i);
        }

        for (int i = 0; i < length; i++)
        {
            if (reverse < lyndonWord)
                return false;

            long bit = reverse % 2;
            reverse /= 2;
            reverse += bit << shift;
        }

        return true;
    }

#if MULTITHREADING
    [ThreadStatic]
#endif
    static LongSet _left = new LongSet();
#if MULTITHREADING
    [ThreadStatic]
#endif
    static LongSet _right = new LongSet();

    static bool HasPropertyX(int[,] matrix)
    {
        long[] matrixColumns = GetMatrixColumns(matrix);
        if (matrixColumns.Length == 1)
            return false;

        return HasPropertyXFast(matrixColumns) || MeetInTheMiddle(matrixColumns);
    }

    static bool MeetInTheMiddle(long[] matrixColumns)
    {
        long[] leftColumns = matrixColumns.Take(matrixColumns.Length / 2).ToArray();
        long[] rightColumns = matrixColumns.Skip(matrixColumns.Length / 2).ToArray();

        if (PrepareHashSet(leftColumns, _left) || PrepareHashSet(rightColumns, _right))
            return true;

        foreach (long columnSum in _left.GetValues())
            if (_right.Contains(columnSum))
                return true;

        return false;
    }

    static bool PrepareHashSet(long[] columns, LongSet sums)
    {
        int setSize = (int)System.Numerics.BigInteger.Pow(3, columns.Length);
        sums.Reset(setSize, setSize);
        foreach (long column in columns)
        {
            foreach (long sum in sums.GetValues())
                if (!sums.Add(sum + column) || !sums.Add(sum - column))
                    return true;

            if (!sums.Add(column) || !sums.Add(-column))
                return true;
        }

        return false;
    }

#if MULTITHREADING
    [ThreadStatic]
#endif
    static LongSet _columnSums = new LongSet();

    static bool HasPropertyXFast(long[] matrixColumns)
    {
        int width = matrixColumns.Length;

        int maxColumnCount = width / 3;
        _columnSums.Reset(width, SumOfBinomialCoefficients(width, maxColumnCount));

        int resetBit, setBit;
        for (int k = 1; k <= maxColumnCount; k++)
        {
            uint columnMask = (1u << k) - 1;
            long sum = 0;
            for (int i = 0; i < k; i++)
                sum += matrixColumns[i];

            while (true)
            {
                if (!_columnSums.Add(sum))
                    return true;
                if (!NextColumnMask(columnMask, k, width, out resetBit, out setBit))
                    break;
                columnMask ^= (1u << resetBit) ^ (1u << setBit);
                sum = sum - matrixColumns[resetBit] + matrixColumns[setBit];
            }
        }

        return false;
    }

    // stolen from Peter Taylor
    static bool NextColumnMask(uint mask, int k, int n, out int resetBit, out int setBit)
    {
        int gap = NumberOfTrailingZeros(~mask);
        int next = 1 + NumberOfTrailingZeros(mask & (mask + 1));

        if (((k - gap) & 1) == 0)
        {
            if (gap == 0)
            {
                resetBit = next - 1;
                setBit = next - 2;
            }
            else if (gap == 1)
            {
                resetBit = 0;
                setBit = 1;
            }
            else
            {
                resetBit = gap - 2;
                setBit = gap;
            }
        }
        else
        {
            if (next == n)
            {
                resetBit = 0;
                setBit = 0;
                return false;
            }

            if ((mask & (1 << next)) == 0)
            {
                if (gap == 0)
                {
                    resetBit = next - 1;
                    setBit = next;
                }
                else
                {
                    resetBit = gap - 1;
                    setBit = next;
                }
            }
            else
            {
                resetBit = next;
                setBit = gap;
            }
        }

        return true;
    }

    static long[] GetMatrixColumns(int[,] matrix)
    {
        int width = matrix.GetLength(0);
        int height = matrix.GetLength(1);

        long[] result = new long[width];
        for (int x = 0; x < width; x++)
        {
            long column = 0;
            for (int y = 0; y < height; y++)
            {
                column *= 13;
                if (matrix[x, y] == 1)
                    column++;
            }

            result[x] = column;
        }

        return result;
    }

    static int SumOfBinomialCoefficients(int n, int k)
    {
        int result = 0;
        for (int i = 0; i <= k; i++)
            result += BinomialCoefficient(n, i);
        return result;
    }

    static int BinomialCoefficient(int n, int k)
    {
        long result = 1;
        for (int i = n - k + 1; i <= n; i++)
            result *= i;
        for (int i = 2; i <= k; i++)
            result /= i;
        return (int)result;
    }

    static void PrintMatrix(int[,] matrix)
    {
        int width = matrix.GetLength(0);
        int height = matrix.GetLength(1);

        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
                Console.Write(matrix[x, y]);
            Console.WriteLine();
        }

        Console.WriteLine("Score: {0}/{1} = {2}", width, height, (double)width / height);
    }
}


class LongSet
{
    private static readonly int[] primes =
    {
        17, 37, 67, 89, 113, 149, 191, 239, 307, 389, 487, 613, 769, 967, 1213, 1523, 1907,
        2389, 2999, 3761, 4703, 5879, 7349, 9187, 11489, 14369, 17971, 22469, 28087, 35111,
        43889, 54869, 68597, 85751, 107197, 133999, 167521, 209431, 261791, 327247, 409063,
        511333, 639167, 798961, 998717, 1248407, 1560511, 1950643, 2438309, 3047909,
        809891, 4762367, 5952959, 7441219, 9301529, 11626913, 14533661, 18167089, 22708867,
        28386089, 35482627, 44353297, 55441637, 69302071, 86627603, 108284507, 135355669,
        169194593, 211493263, 264366593, 330458263, 413072843, 516341057, 645426329,
        806782913, 1008478649, 1260598321
    };

    private int[] _buckets;
    private int[] _nextItemIndexes;
    private long[] _items;
    private int _count;
    private int _minCapacity;
    private int _maxCapacity;
    private int _currentCapacity;

    public LongSet()
    {
        Initialize(0, 0);
    }

    private int GetPrime(int capacity)
    {
        foreach (int prime in primes)
            if (prime >= capacity)
                return prime;

        return int.MaxValue;
    }

    public void Reset(int minCapacity, int maxCapacity)
    {
        if (maxCapacity > _maxCapacity)
            Initialize(minCapacity, maxCapacity);
        else
            ClearBuckets();
    }

    private void Initialize(int minCapacity, int maxCapacity)
    {
        _minCapacity = GetPrime(minCapacity);
        _maxCapacity = GetPrime(maxCapacity);
        _currentCapacity = _minCapacity;

        _buckets = new int[_maxCapacity];
        _nextItemIndexes = new int[_maxCapacity];
        _items = new long[_maxCapacity];
        _count = 0;
    }

    private void ClearBuckets()
    {
        Array.Clear(_buckets, 0, _currentCapacity);
        _count = 0;
        _currentCapacity = _minCapacity;
    }

    public bool Add(long value)
    {
        int bucket = (int)((ulong)value % (ulong)_currentCapacity);
        for (int i = _buckets[bucket] - 1; i >= 0; i = _nextItemIndexes[i])
            if (_items[i] == value)
                return false;

        if (_count == _currentCapacity)
        {
            Grow();
            bucket = (int)((ulong)value % (ulong)_currentCapacity);
        }

        int index = _count;
        _items[index] = value;
        _nextItemIndexes[index] = _buckets[bucket] - 1;
        _buckets[bucket] = index + 1;
        _count++;

        return true;
    }

    private void Grow()
    {
        Array.Clear(_buckets, 0, _currentCapacity);

        const int growthFactor = 8;
        int newCapacity = GetPrime(_currentCapacity * growthFactor);
        if (newCapacity > _maxCapacity)
            newCapacity = _maxCapacity;
        _currentCapacity = newCapacity;

        for (int i = 0; i < _count; i++)
        {
            int bucket = (int)((ulong)_items[i] % (ulong)newCapacity);
            _nextItemIndexes[i] = _buckets[bucket] - 1;
            _buckets[bucket] = i + 1;
        }
    }

    public bool Contains(long value)
    {
        int bucket = (int)((ulong)value % (ulong)_buckets.Length);
        for (int i = _buckets[bucket] - 1; i >= 0; i = _nextItemIndexes[i])
            if (_items[i] == value)
                return true;

        return false;
    }

    public IReadOnlyList<long> GetValues()
    {
        return new ArraySegment<long>(_items, 0, _count);
    }
}

Файл конфігурації:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>

У деяких аспектах ви здаєтесь песимізованими, а не оптимізованими. Єдине, що насправді схоже на оптимізацію, - це дозволяти бітам стискатися, використовуючи ulongта дозволяючи перемотати зсув замість цього BigInteger.
Пітер Тейлор

@PeterTaylor Найважливіша оптимізація - це функція HasPropertyX. Функція повертається, як тільки знайдено перше зіткнення сум стовпців (на відміну від вашої функції scoreLyndonWord). Я також сортував маски стовпців таким чином, щоб спершу перевірити набори стовпців, які мають більше шансів зіткнутися. Ці дві оптимізації покращили продуктивність на порядок.
Субоптимус Прем'єр

Хоча зміни продуктивності часто дивують, в принципі ранній переривання не повинен давати більше, ніж коефіцієнт 2, і GetSumOfColumnsдодає додатковий цикл, який, як я б очікував, коштуватиме більше, ніж коефіцієнт 2. Сортування маски звучить цікаво: можливо, ви могли б редагувати відповідь, щоб трохи поговорити про це? (І в якийсь момент я буду експериментувати з альтернативним способом зробити ранній аборт: причина, яку я не можу зробити, це те, що HashSet не підтримує одночасну ітерацію та модифікацію, але у мене є ідеї, щоб уникнути потреби в ітераторі) .
Пітер Тейлор

2
@justhalf, використовуючи Сіро-еск підхід для перебору підмножин фіксованого розміру є на насправді коштує. Це дозволило мені знайти протягом 26 хвилин 26/14 і 34 з них за дві години, після чого я зробив аборт. В даний час тестую, чи можу я отримати розумний час 28/15.
Пітер Тейлор

1
@ Лембік, я досліджував 29/15 вичерпно за 75,5 годин. 31/16 зайняло б приблизно 3 рази довше - так більше тижня. Ми обоє зробили певні оптимізації, оскільки я почав проводити тест 29/15, тому, можливо, це було б вже до тижня. Ніщо не заважає вам скласти мій код або код SuboptimusPrime і запустити його самостійно, якщо у вас є комп'ютер, на який ви можете залишити його так довго.
Пітер Тейлор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.