속성 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 % 밀도 (즉, 같은 수 01)를 가진 단어에서 시작하여 운동하는 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.