속성 X없이 최고 점수 행렬 찾기


14

이 과제는 부분적으로 알고리즘 과제, 부분적으로 최적화 과제, 부분적으로 가장 빠른 코드 과제입니다.

순환 행렬은 첫 번째 행으로 완전히 지정됩니다 r. 나머지 행은 r오프셋이 행 인덱스와 동일한 행의 각 순환 순열입니다 . 우리는 정사각형이 아닌 순환 행렬을 허용하여 단순히 마지막 행 중 일부가 누락되도록합니다. 그러나 행 수는 열 수를 넘지 않는다고 항상 가정합니다. 예를 들어, 다음 3 x 5 순환 행렬을 고려하십시오.

10111
11011
11101

행렬에 동일한 (벡터) 합계를 가진 동일하지 않은 인덱스를 가진 두 개의 비어 있지 않은 열 집합이 포함되어 있으면 행렬에 속성 X가 있다고 가정합니다. 두 열의 벡터 합계는 단순히 두 열의 요소 별 합계입니다. 즉, x요소를 포함하는 두 열의 합계는 각각 x요소를 포함하는 다른 열입니다 .

위의 행렬은 첫 번째 열과 마지막 열이 동일하므로 속성 X가 있습니다. 항등 행렬에는 속성 X가 없습니다.

위의 행렬의 마지막 열을 제거하면 속성 X가없고 4/3의 점수를주는 예제가 나타납니다.

1011
1101
1110

작업

이 작업은 항목이 모두 0 또는 1이고 속성 X 가없는 최고 점수 순환 행렬을 찾기위한 코드를 작성하는 것 입니다.

점수

점수는 숫자 열을 최고 점수 행렬의 행 수로 나눈 값입니다.

타이 브레이커

두 답변의 점수가 같으면 제출 된 답변이 먼저 이깁니다.

(매우) 누군가가 무제한 점수를 얻는 방법을 찾은 경우, 그러한 해결책에 대한 첫 번째 유효한 증거가 인정됩니다. 유한 행렬의 최적성에 대한 증거를 찾을 수있는 경우는 아니지만 당연히 이길 것입니다.

힌트

12/8 점을 얻는 것은 그리 어렵지 않습니다.

언어와 라이브러리

무료로 사용할 수있는 컴파일러 / 인터프리터 등이있는 모든 언어를 사용할 수 있습니다. Linux 및 Linux 용으로 무료로 제공되는 모든 라이브러리 용.

주요 항목

  • Peter Taylor의 36/19 (자바)
  • Suboptimus Prime (C #)의 32/17
  • justhalf의 21/12 (Python 2)

아, 속성 X는 행이 아닌 열에 있습니다.
— 옵티 마이저

작성된 바와 같이, 1 x 2 행렬 01 은 첫 번째 열의 집합이 빈 집합과 동일한 벡터 합을 갖기 때문에 속성 X를 갖습니다. 비어 있지 않은 열 집합을 의미 했습니까? 나는 그것을 바꾸지 않는 것이 더 깨끗하다고 ​​생각합니다.
— xnor

2
규칙을 가장 쉽게 읽는 것은 여전히 01속성 X : (1) = (0) + (1)입니다. 이를 제외하려면 두 열 세트를 분리해야합니다.
— 피터 테일러

1
이 질문은 (안타깝게도 NP- 하드 인 속성 X를 확인하는 것이 얼마나 어려운지에 대해)이 문제에 대한 많은 통찰력을 줄 것입니다. mathoverflow.net/questions/157634/…
— justhalf

3
현재 우리는 2^m속성 X를 확인하기 위해 모든 열 조합을 무차별 강제 적용하고 있습니다 . "중간 만남"체계 ( "subset sum"문제 참조)를 어떻게 든 고안 할 수 있다면 아마도이를 줄일 수 있습니다 m * 2^(m/2).
— kennytm

답변:


11

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

이것은 많은 아이디어를 사용하여 검색 공간과 비용을 줄입니다. 이전 버전의 코드에 대한 자세한 내용은 개정 내역을 참조하십시오.

  • wlog는 첫 번째 행이 Lyndon 단어 인 순환 행렬 만 고려할 수 있음이 분명합니다 . 단어가 프라임이 아닌 경우 속성 X가 있어야하며 그렇지 않으면 점수 또는 속성 X에 영향을주지 않고 회전 할 수 있습니다.
  • 관찰 된 짧은 수상자의 휴리스틱에 기초하여, 나는 이제 50 % 밀도 (즉, 같은 수 0와 1)를 가진 단어에서 시작하여 운동하는 Lyndon 단어를 반복하고 있습니다 . 나는 고정 밀도 목걸이와 Lyndon 단어에 대해 회색 코드에 설명 된 알고리즘 을 일정한 상각 시간 에 사용합니다 (Sawada and Williams, Theorytical Computer Science 502 (2013) : 46-54).
  • 경험적 관찰은 값이 쌍으로 발생한다는 것입니다. 내가 찾은 각각의 최적의 Lyndon 단어는 반전과 같은 점수를 갖습니다. 따라서 각 쌍의 절반 만 고려하면 속도가 2 배 증가합니다.
  • 내 원래 코드는 BigInteger정확한 테스트를 위해 작동했습니다 . 나는 모듈로를 큰 프라임으로 작동하고 모든 것을 프리미티브에 유지함으로써 잘못된 부정의 위험이있는 속도를 크게 향상시킵니다. 내가 선택한 소수는 2 57 보다 작은 것 중 가장 큰 것인데 , 이는 오버플로없이 내 기본 벡터 표현의 기초를 곱할 수 있기 때문입니다.
  • 서브 옵티 머스 프라임 의 휴리스틱을 도용 했습니다. 크기의 순서대로 하위 집합을 고려하여 빠른 거부를 얻을 수 있습니다. 나는 이제 그 아이디어를 3 진 서브셋 중간에 밋밋한 접근 방식과 병합하여 서브셋 충돌을 테스트했습니다. ( 정수 하위 집합 문제의 접근 방식을 수정하려고 제안한 KennyTM 에 감사의 말 을 전합니다. xnor 과 저는 거의 동시에 작업을 수행하는 방법을 보았습니다). 각 열을 0 또는 1 회 포함 할 수 있고 동일한 합계를 갖는 두 개의 하위 집합을 찾는 대신 각 열 -1, 0 또는 1 회를 포함하고 합계를 0으로 할 수있는 하나의 하위 집합을 찾습니다. 메모리 요구 사항이 크게 줄어 듭니다.
  • 각 요소에 {-1,0,1}^m부정이 있으므로 두 요소 {-1,0,1}^m중 하나만 저장하면 된다는 사실을 관찰함으로써 메모리 요구 사항을 두 가지 절약 할 수있는 추가 요소가 있습니다.
  • 또한 사용자 지정 해시 맵 구현을 사용하여 메모리 요구 사항과 성능을 향상시킵니다. 36/19를 테스트하려면 3 ^ 18 합계 저장이 필요하고 3 ^ 18 long은 오버 헤드없이 거의 3GB입니다 .4GB로는 충분하지 않기 때문에 6GB의 힙을주었습니다. RAM의 8GB 내에서 더 이상 (즉, 테스트 38/20) 가려면 long보다는 int를 저장하기 위해 더 최적화해야합니다. 버킷에서 12 비트 + 암시 적 비트를 남기는 합을 생성하는 서브 세트를 말하는 데 20 비트가 필요합니다. 명중하기에 잘못된 충돌이 너무 많을 까 걱정됩니다.
  • 증거의 무게가 우리가보아야한다는 것을 시사하기 때문에 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)

이것은 좋은 개선입니다. Lyndon 단어를 사용하는 것은 2 ^ n 대신 2 ^ n / n 이진 문자열을 첫 번째 행으로 확인하면된다는 것을 의미합니다.

BigInteger의 각 숫자를 행렬 셀로 사용하면 n> 10 일 때 잘못된 대답이 없습니까?
— kennytm

@KennyTM, 두 번째 매개 변수는 기수입니다. 작은 버그가 있습니다 : 내가 사용해야 n보다는 rows이 고장 안전 의미에서이 유효 솔루션을 폐기하기보다는 잘못된 사람을 받아 들일 것입니다 있지만. 또한 결과에 영향을 미치지 않습니다.
— 피터 테일러

1
더 빨리 평가할 수있는 다른 동등한 조건을 찾지 않으면 X 속성 검사는 시간이 많이 걸리기 때문에 실제로이 점수로 제한됩니다. 내가 그렇게 열망 이유의 그 볼 것을 "비 프라임은"속성 X = D 의미
— justhalf

1
@SuboptimusPrime, 나는 그것을 발견 people.math.sfu.ca/~kya17/teaching/math343/16-343.pdf 및 버그가 수정되었습니다. 흥미롭게도 이제 Lyndon 단어를 반복하는 데 사용하는 알고리즘은 k-n-n 하위 집합을 수행하는 관련 알고리즘 클래스 중 하나이므로 일부 코드를 리팩토링하고 공유 할 수 있습니다.
— 피터 테일러

9

파이썬 2-21/12

2-(3/n)항상 존재 하는 것을 증명하는 과정에서n

이 질문 에서 영감을 받아 De Bruijn Sequence 를 사용 하여 가능한 행렬을 무력화했습니다. 그리고 bruteforcing 후에 n=6,7,8,9,10, 나는 최고의 솔루션이 항상 형태라는 패턴을 발견했다 (n, 2n-3).

그래서 저는 그 모양의 행렬을 무차별 화하는 또 다른 방법을 만들었습니다. 그리고 멀티 프로세싱을 사용하여 작업 속도를 높입니다. 16 코어 우분투에서는 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)
[[00 1011 1011 101010]]
 [01101011100101010] [0110]
 [0110111110010100] [0110]
 [10010111001010100] [1000]
 [0110111101010100] [0001]
 [011011100101010] [0110]
 [1001110101010100]
 [0111100101010100]
 [110111001010100]
 [1101010101010100]
 [10010101001010 1 1]
 [1001010101010 1 1 1]]
(12, 21)
점수 : 1.7500

실제 4m9.121s
사용자 42m47.472s
시스 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)
[[11010111011110]]
 [100101111 11110]
 [011011 111111]
 [100101111 1011 10]
 [011011 1011110]
 [100111110111010]
 [0111111101110]
 [011111011101010]
 [111111011101010]
 [11011111101010]]]
(10, 17)
점수 : 1.7000

의 경우 n=7:

(86, 97)
[[0111101011 1]]
 [11011100]
 [1101011 10 1]
 [100101111 1]
 [01101111 1]
 [1001111110]
 [011111 10 1]]
(7, 11)
점수 : 1.5714

OP ( n=8)에 설명 된 형태의 솔루션 :

(227, 239)
[[011011 11110]]
 [1001111 1010]
 [01111111010]
 [11111101010]
 [110111010 1]
 [11011110]
 [1101011 1]
 [1001111 1 1]]
(8, 12)
점수 : 1.5000

그러나 더 나은 것 ( n=8) :

(95, 108)
[[01101010010 1]]
 [1101010100]
 [100101010 1]
 [011011 1011]
 [0110101010]
 [10010101010]
 [00101110010 1]
 [0110101010]]]
(8, 13)
점수 : 1.6250

또한 다음에서 최적의 솔루션을 찾았습니다 n=9.

(103, 118)
[[011011 101010 1]]
 [10011101010]
 [01111001010 1]
 [11010101010]
 [100101010100]
 [10010111001011]
 [00 1011 1011 11]
 [0010111001011]
 [01101010111010]]]
(9, 15)
점수 : 1.6667

코드는 다음과 같습니다. 그것은 단지 무차별적인 힘이지만 적어도 OP의 주장보다 더 나은 것을 찾을 수 있습니다 =)

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 나는 할 수 있다고 생각하지 않습니다. 31이 필요한 벡터의 조합 n >= 31을 확인해야 함을 의미 2^(2n-3) = 2^59합니다. 평생 끝나지 않을 것입니다 = D
— Justhalf

2
다음과 같은 행렬을 항상 얻을 수 있음을 증명할 수 있습니까?n*(2n-3)
— xnor

7

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

편집 : 내 답변에서 오래된 정보가 삭제되었습니다. Peter Taylor와 거의 동일한 알고리즘을 사용하고 있습니다 ( 편집 : 지금은 더 나은 알고리즘을 사용하고있는 것처럼 보입니다). 내 자신의 최적화를 추가했습니다.

  • 나는 같은 벡터 합계 ( 이 KennyTM의 comment에 의해 제 안됨 ) 로 열 세트를 검색하는 "중간에 만나기"전략을 구현했습니다 . 이 전략은 메모리 사용량을 크게 향상 시켰지만 속도가 느리기 때문에 HasPropertyXFast"중간에 미트 (Meet in the Middle)"접근 방식을 사용하기 전에 동일한 합으로 작은 세트가 있는지 빠르게 확인 하는 기능을 추가했습니다 .
  • HasPropertyXFast 함수 에서 열 집합을 반복하는 동안 열 집합을 1 열로 확인한 다음 2, 3 등으로 확인합니다. 열 합의 첫 번째 충돌이 발견되는 즉시 함수가 반환됩니다. 실제로 그것은 일반적으로 수백만이 아닌 몇 백 또는 수천 개의 열 집합을 확인해야 함을 의미합니다.
  • long변수를 사용하여 전체 열과 벡터 합계를 저장하고 비교합니다. 이 접근법은 열을 배열로 비교하는 것보다 적어도 10 배 빠릅니다.
  • 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 함수와 달리 열 합계의 첫 번째 충돌이 발견되는 즉시 함수가 반환됩니다. 또한 충돌 가능성이 높은 열 집합을 먼저 확인하는 방식으로 열 마스크를 정렬했습니다. 이 두 가지 최적화는 성능을 몇 배나 향상 시켰습니다.
— Suboptimus Prime

성능 변경은 종종 놀라운 일이지만, 원칙적으로 조기 중단은 2의 요소 이상을 제공해서는 안되며 2의 요소 GetSumOfColumns보다 더 많은 비용이 소요될 것으로 예상되는 추가 루프를 추가해야합니다. 그것에 대해 조금 이야기하기 위해 답변을 편집? (그리고 어느 시점에서 나는 조기 중단을 수행하는 다른 방법을 실험 할 것입니다. 내가 할 수없는 이유는 HashSet이 동시 반복과 수정을 지원하지 않지만 반복자가 필요하지 않도록 아이디어가 있기 때문입니다) .
— 피터 테일러

2
@justhalf, 고정 크기의 하위 집합을 반복하기 위해 Gray-esque 방식을 사용하는 것이 실제로 가치가 있습니다. 그것은 9 분 안에 26 분의 14 분과 2 시간 만에 34 분의 2를 찾을 수있게하였고, 그 시점에서 나는 중단되었습니다. 현재 합리적인 시간에 28/15를 얻을 수 있는지 테스트 중입니다.
— Peter Taylor

1
@Lembik, 나는 75.5 시간 만에 29/15를 철저히 탐색했습니다. 31/16은 약 3 배의 시간이 걸리므로 일주일 이상이 소요됩니다. 29/15의 테스트를 시작한 이후로 우리 둘 다 최적화를 했으므로 지금은 일주일이 될 것입니다. 내 코드 또는 SuboptimusPrime의 코드를 컴파일하고 오랫동안 사용할 수있는 컴퓨터가 있으면 직접 실행하는 것을 막을 수는 없습니다.
— 피터 테일러
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.