ค้นหาเมทริกซ์เกณฑ์การให้คะแนนสูงสุดโดยไม่มีคุณสมบัติ 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 โดย Peter Taylor (Java)
  • 32/17 โดย Suboptimus Prime (C #)
  • 21/12 โดย justhalf (Python 2)

อาคุณสมบัติ X อยู่ในคอลัมน์ไม่ใช่แถว
เครื่องมือเพิ่มประสิทธิภาพ

ตามที่เขียนเมทริกซ์ขนาด 1 คูณ 2 01 มีคุณสมบัติ X เนื่องจากชุดของคอลัมน์แรกมีผลรวมเวกเตอร์เดียวกับชุดว่าง บางทีคุณอาจหมายถึงชุดคอลัมน์ที่ไม่มีข้อมูล? ฉันคิดว่ามันสะอาดกว่าที่จะไม่เปลี่ยน
xnor

2
อ่านง่ายที่สุดของกฎระเบียบที่ยังคงเป็นที่01มีคุณสมบัติ (1) = (0) + (1)X: หากคุณต้องการยกเว้นสิ่งนั้นคุณควรบอกว่าคอลัมน์ทั้งสองชุดจะต้องแยกจากกัน
Peter Taylor

1
คำถามนี้จะให้ข้อมูลเชิงลึกเกี่ยวกับปัญหานี้ (เกี่ยวกับความยากในการตรวจสอบคุณสมบัติ X ซึ่งเป็นปัญหาที่น่าเสียดายมาก) 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 เราสามารถพิจารณาเมทริกซ์หมุนเวียนเฉพาะซึ่งแถวแรกเป็นคำ Lyndon : ถ้าคำนั้นไม่ใช่แบบเฉพาะนายกจะต้องมีคุณสมบัติ X และมิฉะนั้นเราสามารถหมุนได้โดยไม่กระทบกับคะแนนหรือคุณสมบัติ X
  • จากการวิเคราะห์ฮิวริสติกจากผู้ชนะที่ได้รับการสังเกตุสั้น ๆ ตอนนี้ฉันกำลังวนคำ Lyndon ที่เริ่มต้นด้วยคำที่มีความหนาแน่น 50% (เช่นจำนวนเดียวกัน0และ1) และออกกำลังกาย ฉันใช้อัลกอริทึมที่อธิบายไว้ในรหัสสีเทาสำหรับสร้อยคอที่มีความหนาแน่นคงที่และคำ Lyndon ในเวลาตัดจำหน่ายอย่างคงที่ Sawada and Williams วิทยาศาสตร์คอมพิวเตอร์เชิงทฤษฎี 502 (2013): 46-54
  • การสังเกตเชิงประจักษ์คือค่าที่เกิดขึ้นเป็นคู่: แต่ละคำที่เหมาะสมของลินดอนที่ฉันพบว่ามีคะแนนเหมือนกับการกลับรายการ ดังนั้นฉันจะได้รับปัจจัยเร่งสองครั้งโดยพิจารณาจากครึ่งหนึ่งของแต่ละคู่ดังกล่าวเท่านั้น
  • รหัสเดิมของฉันทำงานด้วยBigIntegerเพื่อให้การทดสอบที่แน่นอน ฉันได้รับการปรับปรุงความเร็วอย่างมีนัยสำคัญที่ความเสี่ยงของการปฏิเสธเชิงลบโดยการดำเนินการโมดูโลที่มีขนาดใหญ่และทำให้ทุกอย่างในดั้งเดิม ไพร์มที่ฉันเลือกคืออันที่ใหญ่ที่สุดมีขนาดเล็กกว่า 2 57ซึ่งอนุญาตให้คูณด้วยฐานของการแทนเวกเตอร์เชิงเส้นของฉันโดยไม่ล้น
  • ฉันได้ขโมยฮิวริสติกของSuboptimus Primeว่าเป็นไปได้ที่จะได้รับการปฏิเสธอย่างรวดเร็วโดยพิจารณาส่วนย่อยตามลำดับที่เพิ่มขึ้น ตอนนี้ฉันได้รวมความคิดนั้นเข้ากับชุดย่อยแบบไตรภาคตรงกลางเพื่อทดสอบการชนกลุ่มย่อย (ให้เครดิตกับKennyTMสำหรับการแนะนำให้พยายามปรับวิธีการจากปัญหาเซตย่อยจำนวนเต็มฉันคิดว่าxnorและฉันเห็นวิธีที่จะทำมันพร้อมกันมากทีเดียว) แทนที่จะค้นหาสองชุดย่อยซึ่งสามารถรวมแต่ละคอลัมน์ 0 หรือ 1 ครั้งและมีผลรวมเดียวกันเรามองหาหนึ่งชุดย่อยซึ่งสามารถรวมแต่ละคอลัมน์ -1, 0 หรือ 1 ครั้งและรวมเป็นศูนย์ สิ่งนี้ลดความต้องการหน่วยความจำลงอย่างมาก
  • มีปัจจัยพิเศษอีกสองข้อในการบันทึกความต้องการของหน่วยความจำโดยการสังเกตว่าเนื่องจากแต่ละองค์ประกอบ{-1,0,1}^mมีการปฏิเสธในตัว{-1,0,1}^mมันจึงมีความจำเป็นเพียงเพื่อเก็บหนึ่งในสอง
  • ฉันยังปรับปรุงข้อกำหนดหน่วยความจำและประสิทธิภาพโดยใช้การปรับใช้ hashmap ที่กำหนดเอง ในการทดสอบ 36/19 ต้องมีการจัดเก็บผลรวม 3 ^ 18 และความยาว 3 ^ 18 เกือบ 3GB โดยไม่มีค่าใช้จ่ายใด ๆ - ฉันให้ 6GB ของกองเพราะ 4GB ไม่เพียงพอ; หากต้องการดำเนินการเพิ่มเติม (เช่นการทดสอบ 38/20) ภายใน 8GB ของ RAM จะต้องมีการปรับแต่งเพิ่มเติมเพื่อจัดเก็บ ints แทนที่จะใช้เวลานาน ด้วย 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)

นี่คือการปรับปรุงที่ดี ดูเหมือนว่าการใช้คำ Lyndon หมายความว่าคุณจะต้องตรวจสอบสตริงไบนารี 2 ^ n / n โดยประมาณสำหรับแถวแรกแทนที่จะเป็น 2 ^ n

ในขณะที่คุณใช้ BigInteger แต่ละตัวเป็นเซลล์เมทริกซ์จะไม่มีคำตอบที่ผิดเมื่อ n> 10
kennytm

@KennyTM โปรดทราบว่าพารามิเตอร์ที่สองคือ Radix มีข้อผิดพลาดเล็ก ๆ : ฉันควรจะใช้nมากกว่าrowsแม้ว่ามันจะไม่ปลอดภัยในแง่ที่ว่ามันจะทิ้งวิธีการแก้ปัญหาที่ถูกต้องแทนที่จะยอมรับข้อผิดพลาด และจะไม่ส่งผลต่อผลลัพธ์
Peter Taylor

1
ฉันคิดว่าเรา จำกัด อยู่ที่คะแนนนี้เนื่องจากการตรวจสอบคุณสมบัติ X นั้นใช้เวลานานมากเว้นแต่เราจะพบเงื่อนไขที่เทียบเท่าซึ่งสามารถประเมินได้เร็วขึ้น นั่นเป็นเหตุผลที่ฉันกระตือรือร้นที่จะเห็นว่า "ไม่ใช่นายก" หมายถึงคุณสมบัติ X = D
justhalf

1
@SuboptimusPrime ฉันพบได้ที่people.math.sfu.ca/~kya17/teaching/math343/16-343.pdfและแก้ไขข้อบกพร่อง ที่น่าสนใจว่าอัลกอริทึมที่ฉันใช้ในการวนซ้ำคำ Lyndon เป็นหนึ่งในคลาสของอัลกอริธึมที่เกี่ยวข้องซึ่งทำหน้าที่ k-of-n เซตย่อยดังนั้นฉันอาจจะสามารถ refactor และแบ่งปันรหัสบางอย่างได้
Peter Taylor

9

Python 2 - 21/12

ในกระบวนการพิสูจน์ว่ามี2-(3/n)อยู่เสมอสำหรับสิ่งใด ๆn

โดยได้รับแรงบันดาลใจจากคำถามนี้ฉันใช้De Bruijn Sequenceเพื่อดุร้ายเมทริกซ์ และหลังจาก bruteforcing สำหรับผมพบว่ารูปแบบที่เป็นทางออกที่สูงที่สุดอยู่เสมอในรูปของการn=6,7,8,9,10(n, 2n-3)

ดังนั้นฉันจึงสร้างวิธีอื่นขึ้นมาเพื่อสร้างรูปร่างของเมทริกซ์นั้นและใช้การประมวลผลแบบทวีคูณเพื่อเร่งให้เร็วขึ้นเนื่องจากงานนี้สามารถแจกจ่ายได้อย่างมาก ใน Ubuntu แบบ 16-core มันสามารถหาวิธีแก้ปัญหา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 1 1 1 1 1 0 0 0 1 1 0 0 1 1 0]
 [0 0 1 0 0 1 0 1 1 1 1 0 0 0 1 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]
 [1 0 0 1 0 1 1 1 1 0 0 0 1 0 0 1 1 0 0 0 0 0]
 [0 0 1 0 1 1 1 1 0 0 0 1 1 0 0 1 1 0 0 0 0 1]
 [0 1 0 1 1 1 1 0 0 0 1 1 0 0 1 1 0 0 0 0 1 0]
 [1 0 1 1 1 1 0 0 0 1 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 1 0 0 1]
 [1 1 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1 1 0 0 1 0]
 [1 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1]
 [1 1 0 0 0 1 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1 1]
 [1 0 0 0 1 0 0 1 1 0 0 0 0 1 1 0 0 1 0 1 1 1]]
(12, 21)
คะแนน: 1.7500

จริง 4m9.121s
ผู้ใช้ 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 1 1 1 1 1 1 0]
 [1 0 1 0 1 0 0 1 1 1 1 0 1 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 1 1 1 1 0 0 1 1 0]
 [0 1 0 0 1 1 1 1 1 1 1 1 0 1 1 1 0 1]
 [1 0 0 1 1 1 1 1 1 1 1 0 1 1 0 0 1 0]
 [0 0 1 1 1 1 0 1 1 1 1 1 1 0 0 1 0 1]
 [0 1 1 1 1 0 1 1 1 0 1 1 0 1 0 1 0 0]
 [1 1 1 1 0 1 1 1 0 1 1 0 1 0 1 0 0 0]
 [1 1 1 0 1 1 1 0 1 1 0 1 0 1 0 0 1 1]]
(10, 17)
คะแนน: 1.7000

สำหรับn=7:

(86, 97)
[[0 1 1 1 0 0 0 0 1 1 1]
 [1 1 1 0 0 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 1 0 1 1 0 0 1]
 [1 1 1 1 1 1 0 1 1 0 0 1 0]
 [1 1 1 1 0 1 1 0 0 1 1 1]
 [1 1 1 0 1 1 0 0 1 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]]
(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 0 1 1 1 0 0 1]
 [0 0 1 1 0 0 0 1 1 0 0 1 0]]
(8, 13)
คะแนน: 1.6250

นอกจากนี้ยังพบวิธีแก้ปัญหาที่เหมาะสมอีกอย่างที่n=9:

(103, 118)
[[0 1 0 1 1 1 0 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 1 1 1 1 1 1 0 0]
 [0 0 1 1 0 0 1 0 1 0 1 1 1 0 0]]
(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 ฉันไม่คิดว่าฉันสามารถ มันต้องใช้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 #)

แก้ไข: ลบข้อมูลที่ล้าสมัยจากคำตอบของฉัน ฉันใช้อัลกอริทึมส่วนใหญ่เหมือนกับ Peter Taylor ( แก้ไข:ดูเหมือนว่าเขาใช้อัลกอริทึมที่ดีกว่าตอนนี้) แม้ว่าฉันจะเพิ่มการเพิ่มประสิทธิภาพของตัวเองแล้ว:

  • ฉันใช้กลยุทธ์ "พบกลาง" เพื่อค้นหาชุดคอลัมน์ด้วยผลรวมเวกเตอร์เดียวกัน (แนะนำโดยความคิดเห็นของ KennyTM นี้ ) กลยุทธ์นี้ปรับปรุงการใช้หน่วยความจำมาก แต่ค่อนข้างช้าดังนั้นฉันจึงเพิ่มHasPropertyXFastฟังก์ชั่นที่จะตรวจสอบว่ามีชุดเล็ก ๆ ที่มีผลรวมเท่ากันก่อนที่จะใช้วิธี "พบตรงกลาง" หรือไม่
  • ในขณะที่วนซ้ำผ่านชุดคอลัมน์ในHasPropertyXFast ฟังก์ชั่นฉันเริ่มจากการตรวจสอบชุดคอลัมน์ด้วย 1 คอลัมน์จากนั้นเป็น 2, 3 และต่อไป ฟังก์ชันจะคืนค่าทันทีที่พบการชนกันครั้งแรกของผลรวมคอลัมน์ ในทางปฏิบัติหมายความว่าฉันมักจะต้องตรวจสอบชุดคอลัมน์สองสามร้อยหรือหลายพันแทนที่จะเป็นล้าน
  • ฉันใช้longตัวแปรเพื่อจัดเก็บและเปรียบเทียบคอลัมน์ทั้งหมดกับผลรวมของเวกเตอร์ วิธีการนี้อย่างน้อยลำดับของขนาดเร็วกว่าการเปรียบเทียบคอลัมน์เป็นอาร์เรย์
  • ฉันได้เพิ่มการใช้งาน hashset ของฉันเองซึ่งได้รับการปรับให้เหมาะกับ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
Peter Taylor

@PeterTaylor การเพิ่มประสิทธิภาพที่สำคัญที่สุดคือในฟังก์ชัน HasPropertyX ฟังก์ชันจะส่งคืนทันทีที่พบการชนกันครั้งแรกของผลรวมคอลัมน์ (ไม่เหมือนกับฟังก์ชัน scoreLyndonWord ของคุณ) ฉันได้จัดเรียงมาสก์คอลัมน์ด้วยวิธีที่เราตรวจสอบชุดคอลัมน์ที่มีแนวโน้มที่จะชนกันมากขึ้น การเพิ่มประสิทธิภาพทั้งสองนี้ช่วยปรับปรุงประสิทธิภาพตามลำดับความสำคัญ
Suboptimus Prime

ถึงแม้ว่าการเปลี่ยนแปลงของประสิทธิภาพมักจะน่าประหลาดใจ แต่โดยหลักการแล้วการยกเลิกก่อนกำหนดไม่ควรให้มากกว่า 2 เท่าและGetSumOfColumnsเพิ่มลูปพิเศษซึ่งฉันคาดว่าจะมีราคาสูงกว่าปัจจัยที่ 2 การเรียงลำดับหน้ากากฟังดูน่าสนใจ: บางทีคุณอาจจะทำได้ แก้ไขคำตอบเพื่อพูดคุยเกี่ยวกับเรื่องนี้หรือไม่? (และในบางจุดฉันจะทดลองด้วยวิธีอื่นในการยกเลิกต้น: เหตุผลที่ฉันไม่สามารถทำได้คือ HashSet ไม่สนับสนุนการทำซ้ำและการแก้ไขพร้อมกัน แต่ฉันมีความคิดที่จะหลีกเลี่ยงความต้องการตัวทำซ้ำ) .
Peter Taylor

2
@justhalf โดยใช้แนวทางสีเทาสำหรับวนซ้ำของขนาดคงที่จะคุ้มค่าจริง ๆ มันทำให้ฉันพบ 26/14 ภายในไม่ถึง 9 นาทีและ 34 ของพวกเขาภายในสองชั่วโมงซึ่งฉันได้ยกเลิก กำลังทดสอบเพื่อดูว่าฉันสามารถรับ 28/15 ในเวลาที่เหมาะสม
Peter Taylor

1
@ Lembik ฉันสำรวจ 29/15 ใน 75.5 ชั่วโมงอย่างละเอียด 31/16 จะใช้เวลาประมาณ 3 ครั้งนานกว่าสัปดาห์ เราทั้งคู่ต่างก็ปรับตัวเพิ่มประสิทธิภาพตั้งแต่ฉันเริ่มทำการทดสอบที่ 29/15 ดังนั้นอาจจะลดลงไปหนึ่งสัปดาห์ในขณะนี้ ไม่มีอะไรหยุดคุณจากการรวบรวมรหัสของฉันหรือรหัส SuboptimusPrime และรันด้วยตัวคุณเองถ้าคุณมีคอมพิวเตอร์ที่คุณสามารถทิ้งไว้ได้นาน
Peter Taylor
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.