Temukan set independen terbesar dalam grafik seperti kisi-dimensi tinggi

16

Untuk bilangan bulat positif yang diberikan n, pertimbangkan semua panjang string biner 2n-1. Untuk string tertentu S, biarkan Lmenjadi array panjang nyang berisi hitungan jumlah 1s di setiap substring panjang ndari S. Misalnya, jika n=3dan S = 01010kemudian L=[1,2,1]. Kami menyebutnya Larray penghitungan S.

Kami mengatakan bahwa dua string S1dan S2panjang yang sama cocok jika masing-masing array array L1dan L2memiliki properti itu L1[i] <= 2*L2[i]dan L2[i] <= 2*L1[i]untuk semua i.

Tugas

Untuk meningkatkan nmulai dari n=1, tugasnya adalah untuk menemukan ukuran set string terbesar, masing-masing panjang 2n-1sehingga tidak ada dua string yang cocok.

Kode Anda harus menampilkan satu nomor per nilai n.

Skor

Skor Anda adalah yang tertinggi di nmana tidak ada orang lain yang memposting jawaban yang benar lebih tinggi untuk semua jawaban Anda. Jelas jika Anda memiliki semua jawaban optimal maka Anda akan mendapatkan skor untuk tertinggi yang nAnda posting. Namun, bahkan jika jawaban Anda tidak optimal, Anda masih bisa mendapatkan skor jika tidak ada orang lain yang bisa mengalahkannya.

Contoh jawaban

Karena n=1,2,3,4aku mengerti 2,4,10,16.

Bahasa dan perpustakaan

Anda dapat menggunakan bahasa dan perpustakaan yang tersedia yang Anda suka. Jika memungkinkan, alangkah baiknya untuk dapat menjalankan kode Anda jadi harap sertakan penjelasan lengkap tentang cara menjalankan / kompilasi kode Anda di linux jika memungkinkan.

Entri terkemuka

  • 5 oleh Martin Büttner di Mathematica
  • 6 oleh Reto Koradi di C ++ . Nilai adalah 2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086. 5 pertama diketahui optimal.
  • 7 oleh Peter Taylor di Jawa . Nilai adalah 2, 4, 10, 16, 31, 47, 76, 111, 166, 235.
  • 9 oleh joriki di Jawa . Nilai adalah 2, 4, 10, 16, 31, 47, 76, 112, 168.

sumber
3
Saya pikir lebih alami untuk memahami ketidaksetaraan ketika dinotasikan sebagai L1[i]/2 <= L2[i] <= 2*L1[i].
orlp
1
Juga, pencocokan bukanlah hubungan ekivalensi. match(A, B)dan match(B, C)tidak menyiratkan match(A, C)(sama untuk kebalikannya). Contoh: [1] dan [2] cocok, [2] dan [3] cocok, tetapi [1] dan [3] tidak. Demikian pula, [1,3] dan [3,1] tidak cocok, [3, 1] dan [2, 3] tidak cocok, tetapi [1, 3] dan [2, 3] cocok.
orlp

Jawaban:

7

2, 4, 10, 16, 31, 47, 76, 112, 168

Untuk setiap n, kode Java ini menentukan array penghitungan yang mungkin dan kemudian menemukan set peningkatan ukuran yang tidak cocok, untuk setiap ukuran dimulai dengan set acak dan memperbaikinya dengan penurunan curam secara acak. Di setiap langkah, salah satu elemen dari himpunan dipilih secara acak seragam dan diganti dengan array penghitungan yang dipilih secara acak di antara yang tidak digunakan. Langkah ini diterima jika tidak menambah jumlah kecocokan. Resep yang terakhir ini tampaknya sangat penting; menerima langkah hanya jika mereka mengurangi jumlah kecocokan hampir tidak efektif. Langkah-langkah meninggalkan jumlah pertandingan yang invarian memungkinkan ruang pencarian untuk dieksplorasi, dan akhirnya beberapa ruang mungkin terbuka untuk menghindari salah satu pertandingan. Setelah 2 ^ 24 langkah tanpa peningkatan, ukuran sebelumnya adalah output untuk nilai sekarang dari n, dan n bertambah.

Hasil hingga n = 9 adalah 2, 4, 10, 16, 31, 47, 76, 112, 168, meningkatkan hasil sebelumnya untuk n = 8 per satu dan untuk n = 9 oleh dua. Untuk nilai n yang lebih tinggi, batas 2 ^ 24 langkah mungkin harus ditingkatkan.

Saya juga mencoba annealing yang disimulasikan (dengan jumlah kecocokan sebagai energi), tetapi tanpa peningkatan pada penurunan yang curam.

Kode

save as Question54354.java
compile with javac Question54354.java
run withjava Question54354

import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class Question54354 {
    static class Array {
        int [] arr;

        public Array (int [] arr) {
            this.arr = arr;
        }

        public int hashCode () {
            return Arrays.hashCode (arr);
        }

        public boolean equals (Object o) {
            return Arrays.equals (((Array) o).arr,arr);
        }
    }

    static int [] indices;
    static int [] [] counts;
    static boolean [] used;

    static Random random = new Random (0);

    static boolean match (int [] c1,int [] c2) {
        for (int k = 0;k < c1.length;k++)
            if (c1 [k] > 2 * c2 [k] || c2 [k] > 2 * c1 [k])
                return false;
        return true;
    }

    static int matches (int i) {
        int result = 0;
        for (int j = 0;j < indices.length;j++)
            if (j != i && match (counts [indices [i]],counts [indices [j]]))
                result++;
        return result;
    }

    static void randomize (int i) {
        do
            indices [i] = random.nextInt (counts.length);
        while (used [indices [i]]);
    }

    public static void main (String [] args) {
        for (int n = 1,length = 1;;n++,length += 2) {
            int [] lookup = new int [1 << n];
            for (int string = 0;string < 1 << n;string++)
                for (int bit = 1;bit < 1 << n;bit <<= 1)
                    if ((string & bit) != 0)
                        lookup [string]++;
            Set<Array> arrays = new HashSet<Array> ();
            for (int string = 0;string < 1 << length;string++) {
                int [] count = new int [n];
                for (int i = 0;i < n;i++)
                    count [i] = lookup [(string >> i) & ((1 << n) - 1)];
                arrays.add (new Array (count));
            }
            counts = new int [arrays.size ()] [];
            int j = 0;
            for (Array array : arrays)
                counts [j++] = array.arr;
            used = new boolean [counts.length];

            int m;
            outer:
            for (m = 1;m <= counts.length;m++) {
                indices = new int [m];
                for (;;) {
                    Arrays.fill (used,false);
                    for (int i = 0;i < m;i++) {
                        randomize (i);
                        used [indices [i]] = true;
                    }
                    int matches = 0;
                    for (int i = 0;i < m;i++)
                        matches += matches (i);
                    matches /= 2;
                    int stagnation = 0;
                    while (matches != 0) {
                        int k = random.nextInt (m);
                        int oldMatches = matches (k);
                        int oldIndex = indices [k];
                        randomize (k);
                        int newMatches = matches (k);
                        if (newMatches <= oldMatches) {
                            if (newMatches < oldMatches) {
                                matches += newMatches - oldMatches;
                                stagnation = 0;
                            }
                            used [oldIndex] = false;
                            used [indices [k]] = true;
                        }
                        else
                            indices [k] = oldIndex;

                        if (++stagnation == 0x1000000)
                            break outer;
                    }
                    break;
                }
            }
            System.out.println (n + " : " + (m - 1));
        }
    }
}
joriki
sumber
1
Perbaikan yang sangat bagus!
11

2, 4, 10, 16, 31, 47, 76, 111, 166, 235

Catatan

Jika kita mempertimbangkan grafik Gdengan simpul 0ke ndan ujung-ujungnya bergabung dengan dua angka yang cocok, maka daya tensor G^n memiliki simpul yang (x_0, ..., x_{n-1})membentuk daya Cartesian {0, ..., n}^ndan tepi antara tupel yang cocok. Grafik yang menarik adalah subgraf yang G^n diinduksi oleh simpul-simpul yang sesuai dengan "array penghitungan" yang memungkinkan.

Jadi subtugas pertama adalah untuk menghasilkan simpul tersebut. Pendekatan naif menyebutkan lebih dari 2^{2n-1}string, atau pada urutan 4^n. Tetapi jika kita melihat array perbedaan pertama dari array penghitungan kita menemukan bahwa hanya ada 3^nkemungkinan, dan dari perbedaan pertama kita dapat menyimpulkan kisaran nilai awal yang mungkin dengan mensyaratkan bahwa tidak ada elemen dalam perbedaan nol yang kurang dari 0atau lebih besar dari n.

Kami kemudian ingin menemukan set independen maksimum. Saya menggunakan satu teorema dan dua heuristik:

  • Teorema: himpunan independen maksimum dari gabungan grafik yang terpisah adalah penyatuan himpunan independen maksimumnya. Jadi jika kita memecah grafik menjadi komponen yang tidak terhubung, kita dapat menyederhanakan masalah.
  • Heuristik: menganggap bahwa (n, n, ..., n)akan berada dalam set independen maksimum. Ada sekelompok besar simpul di {m, m+1, ..., n}^nmana mbilangan bulat terkecil yang cocok n; (n, n, ..., n)dijamin tidak memiliki kecocokan di luar klik itu.
  • Heuristik: ambil pendekatan serakah dalam memilih titik terendah.

Di komputer saya temuan ini 111untuk n=8di 16 detik, 166untuk n=9sekitar 8 menit, dan 235untuk n=10sekitar 2 jam.

Kode

Simpan sebagai PPCG54354.java, kompilasi sebagai javac PPCG54354.java, dan jalankan sebagai java PPCG54354.

import java.util.*;

public class PPCG54354 {
    public static void main(String[] args) {
        for (int n = 1; n < 20; n++) {
            long start = System.nanoTime();

            Set<Vertex> constructive = new HashSet<Vertex>();
            for (int i = 0; i < (int)Math.pow(3, n-1); i++) {
                int min = 0, max = 1, diffs[] = new int[n-1];
                for (int j = i, k = 0; k < n-1; j /= 3, k++) {
                    int delta = (j % 3) - 1;
                    if (delta == -1) min++;
                    if (delta != 1) max++;
                    diffs[k] = delta;
                }

                for (; min <= max; min++) constructive.add(new Vertex(min, diffs));
            }

            // Heuristic: favour (n, n, ..., n)
            Vertex max = new Vertex(n, new int[n-1]);
            Iterator<Vertex> it = constructive.iterator();
            while (it.hasNext()) {
                Vertex v = it.next();
                if (v.matches(max) && !v.equals(max)) it.remove();
            }

            Set<Vertex> ind = independentSet(constructive, n);
            System.out.println(ind.size() + " after " + ((System.nanoTime() - start) / 1000000000L) + " secs");
        }
    }

    private static Set<Vertex> independentSet(Set<Vertex> vertices, int dim) {
        if (vertices.size() < 2) return vertices;

        for (int idx = 0; idx < dim; idx++) {
            Set<Set<Vertex>> p = connectedComponents(vertices, idx);
            if (p.size() > 1) {
                Set<Vertex> ind = new HashSet<Vertex>();
                for (Set<Vertex> part : connectedComponents(vertices, idx)) {
                    ind.addAll(independentSet(part, dim));
                }
                return ind;
            }
        }

        // Greedy
        int minMatches = Integer.MAX_VALUE;
        Vertex minV = null;
        for (Vertex v0 : vertices) {
            int numMatches = 0;
            for (Vertex vi : vertices) if (v0.matches(vi)) numMatches++;
            if (numMatches < minMatches) {
                minMatches = numMatches;
                minV = v0;
            }
        }

        Set<Vertex> nonmatch = new HashSet<Vertex>();
        for (Vertex vi : vertices) if (!minV.matches(vi)) nonmatch.add(vi);
        Set<Vertex> ind = independentSet(nonmatch, dim);
        ind.add(minV);
        return ind;
    }

    // Separates out a set of vertices which form connected components when projected into the idx axis.
    private static Set<Set<Vertex>> connectedComponents(Set<Vertex> vertices, final int idx) {
        List<Vertex> sorted = new ArrayList<Vertex>(vertices);
        Collections.sort(sorted, new Comparator<Vertex>() {
                public int compare(Vertex a, Vertex b) {
                    return a.x[idx] - b.x[idx];
                }
            });

        Set<Set<Vertex>> connectedComponents = new HashSet<Set<Vertex>>();
        Set<Vertex> current = new HashSet<Vertex>();
        int currentVal = 0;
        for (Vertex v : sorted) {
            if (!match(currentVal, v.x[idx]) && !current.isEmpty()) {
                connectedComponents.add(current);
                current = new HashSet<Vertex>();
            }

            current.add(v);
            currentVal = v.x[idx];
        }

        if (!current.isEmpty()) connectedComponents.add(current);
        return connectedComponents;
    }

    private static boolean match(int a, int b) {
        return a <= 2 * b && b <= 2 * a;
    }

    private static class Vertex {
        final int[] x;
        private final int h;

        Vertex(int[] x) {
            this.x = x.clone();

            int _h = 0;
            for (int xi : x) _h = _h * 37 + xi;
            h = _h;
        }

        Vertex(int x0, int[] diffs) {
            x = new int[diffs.length + 1];
            x[0] = x0;
            for (int i = 0; i < diffs.length; i++) x[i+1] = x[i] + diffs[i];

            int _h = 0;
            for (int xi : x) _h = _h * 37 + xi;
            h = _h;
        }

        public boolean matches(Vertex v) {
            if (v == this) return true;
            if (x.length != v.x.length) throw new IllegalArgumentException("v");
            for (int i = 0; i < x.length; i++) {
                if (!match(x[i], v.x[i])) return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            return h;
        }

        @Override
        public boolean equals(Object obj) {
            return (obj instanceof Vertex) && equals((Vertex)obj);
        }

        public boolean equals(Vertex v) {
            if (v == this) return true;
            if (x.length != v.x.length) return false;
            for (int i = 0; i < x.length; i++) {
                if (x[i] != v.x[i]) return false;
            }
            return true;
        }

        @Override
        public String toString() {
            if (x.length == 0) return "e";

            StringBuilder sb = new StringBuilder(x.length);
            for (int xi : x) sb.append(xi < 10 ? (char)('0' + xi) : (char)('A' + xi - 10));
            return sb.toString();
        }
    }
}
Peter Taylor
sumber
10

Mathematica n = 5,, 31 string

Saya baru saja menulis solusi brute force menggunakan built-in Mathematica untuk memverifikasi jawaban contoh Lembik, tetapi dapat menangani n = 5juga:

n = 5;
s = Tuples[{0, 1}, 2 n - 1];
l = Total /@ Partition[#, n, 1] & /@ s
g = Graph[l, 
  Cases[Join @@ Outer[UndirectedEdge, l, l, 1], 
   a_ <-> b_ /; 
    a != b && And @@ Thread[a <= 2 b] && And @@ Thread[b <= 2 a]]]
set = First@FindIndependentVertexSet[g]
Length@set

Sebagai bonus, kode ini menghasilkan visualisasi masalah sebagai grafik di mana setiap sisi menunjukkan dua string yang cocok.

Ini adalah grafik untuk n = 3:

masukkan deskripsi gambar di sini

Martin Ender
sumber
2
Awalnya saya pikir grafiknya simetris dengan baik, tetapi kemudian saya melihat titik agak offset di sebelah kiri. Tidak dapat melihat :(
orlp
3

C ++ (heuristik): 2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086

Ini sedikit di belakang hasil Peter Taylor, menjadi 1 sampai 3 lebih rendah untuk n=7, 9dan 10. Keuntungannya adalah lebih cepat, jadi saya bisa menjalankannya untuk nilai yang lebih tinggi n. Dan itu bisa dipahami tanpa matematika mewah. ;)

Kode saat ini dibuat untuk menjalankan hingga n=15. Jalankan kali meningkat kira-kira dengan faktor 4 untuk setiap peningkatan n. Misalnya, 0,008 detik untuk n=7, 0,07 detik untuk n=9, 1,34 detik untuk n=11, 27 detik untuk n=13, dan 9 menit untuk n=15.

Ada dua pengamatan utama yang saya gunakan:

  • Alih-alih beroperasi pada nilai-nilai itu sendiri, heuristik beroperasi pada penghitungan array. Untuk melakukan ini, daftar semua array penghitungan unik dihasilkan terlebih dahulu.
  • Menggunakan array penghitungan dengan nilai-nilai kecil lebih menguntungkan, karena mereka menghilangkan lebih sedikit ruang solusi. Ini didasarkan pada setiap penghitungan ctidak termasuk rentang c / 2hingga 2 * cdari nilai lain. Untuk nilai yang lebih kecil c, rentang ini lebih kecil, yang berarti bahwa nilai yang lebih sedikit dikecualikan dengan menggunakannya.

Hasilkan Array Penghitungan Unik

Saya menggunakan brute force yang satu ini, mengulangi semua nilai, menghasilkan array jumlah untuk masing-masing, dan menyatukan daftar yang dihasilkan. Ini tentu saja dapat dilakukan dengan lebih efisien, tetapi cukup baik untuk jenis nilai yang kami operasikan.

Ini sangat cepat untuk nilai-nilai kecil. Untuk nilai yang lebih besar, biaya overhead menjadi substansial. Misalnya, untuk n=15, ia menggunakan sekitar 75% dari keseluruhan runtime. Ini pasti akan menjadi area untuk dilihat ketika mencoba untuk pergi jauh lebih tinggi daripada n=15. Bahkan penggunaan memori untuk membangun daftar array penghitungan untuk semua nilai akan mulai menjadi masalah.

Jumlah array penghitungan unik adalah sekitar 6% dari jumlah nilai untuk n=15. Jumlah relatif ini menjadi lebih kecil karena nmenjadi lebih besar.

Pilihan Serakah Menghitung Nilai Array

Bagian utama dari algoritma memilih penghitungan nilai array dari daftar yang dihasilkan menggunakan pendekatan serakah sederhana.

Berdasarkan teori bahwa menggunakan array penghitungan dengan jumlah kecil bermanfaat, array penghitungan diurutkan berdasarkan jumlah penghitungannya.

Mereka kemudian diperiksa secara berurutan, dan nilai dipilih jika itu kompatibel dengan semua nilai yang digunakan sebelumnya. Jadi ini melibatkan satu lintasan linear tunggal melalui array penghitungan yang unik, di mana setiap kandidat dibandingkan dengan nilai-nilai yang sebelumnya dipilih.

Saya punya beberapa ide tentang bagaimana heuristik berpotensi ditingkatkan. Tapi ini sepertinya titik awal yang masuk akal, dan hasilnya terlihat cukup bagus.

Kode

Ini tidak sangat dioptimalkan. Saya memiliki struktur data yang lebih rumit di beberapa titik, tetapi akan membutuhkan lebih banyak pekerjaan untuk menggeneralisasikannya di luar n=8, dan perbedaan kinerja tampaknya tidak terlalu besar.

#include <cstdint>
#include <cstdlib>
#include <vector>
#include <algorithm>
#include <sstream>
#include <iostream>

typedef uint32_t Value;

class Counter {
public:
    static void setN(int n);

    Counter();
    Counter(Value val);

    bool operator==(const Counter& rhs) const;
    bool operator<(const Counter& rhs) const;

    bool collides(const Counter& other) const;

private:
    static const int FIELD_BITS = 4;
    static const uint64_t FIELD_MASK = 0x0f;

    static int m_n;
    static Value m_valMask;

    uint64_t fieldSum() const;

    uint64_t m_fields;
};

void Counter::setN(int n) {
    m_n = n;
    m_valMask = (static_cast<Value>(1) << n) - 1;
}

Counter::Counter()
  : m_fields(0) {
}

Counter::Counter(Value val) {
    m_fields = 0;
    for (int k = 0; k < m_n; ++k) {
        m_fields <<= FIELD_BITS;
        m_fields |= __builtin_popcount(val & m_valMask);
        val >>= 1;
    }
}

bool Counter::operator==(const Counter& rhs) const {
    return m_fields == rhs.m_fields;
}

bool Counter::operator<(const Counter& rhs) const {
    uint64_t lhsSum = fieldSum();
    uint64_t rhsSum = rhs.fieldSum();
    if (lhsSum < rhsSum) {
        return true;
    }
    if (lhsSum > rhsSum) {
        return false;
    }

    return m_fields < rhs.m_fields;
}

bool Counter::collides(const Counter& other) const {
    uint64_t fields1 = m_fields;
    uint64_t fields2 = other.m_fields;

    for (int k = 0; k < m_n; ++k) {
        uint64_t c1 = fields1 & FIELD_MASK;
        uint64_t c2 = fields2 & FIELD_MASK;

        if (c1 > 2 * c2 || c2 > 2 * c1) {
            return false;
        }

        fields1 >>= FIELD_BITS;
        fields2 >>= FIELD_BITS;
    }

    return true;
}

int Counter::m_n = 0;
Value Counter::m_valMask = 0;

uint64_t Counter::fieldSum() const {
    uint64_t fields = m_fields;
    uint64_t sum = 0;
    for (int k = 0; k < m_n; ++k) {
        sum += fields & FIELD_MASK;
        fields >>= FIELD_BITS;
    }

    return sum;
}

typedef std::vector<Counter> Counters;

int main(int argc, char* argv[]) {
    int n = 0;
    std::istringstream strm(argv[1]);
    strm >> n;

    Counter::setN(n);

    int nBit = 2 * n - 1;
    Value maxVal = static_cast<Value>(1) << nBit;

    Counters allCounters;

    for (Value val = 0; val < maxVal; ++val) {
        Counter counter(val);
        allCounters.push_back(counter);
    }

    std::sort(allCounters.begin(), allCounters.end());

    Counters::iterator uniqEnd =
        std::unique(allCounters.begin(), allCounters.end());
    allCounters.resize(std::distance(allCounters.begin(), uniqEnd));

    Counters solCounters;
    int nSol = 0;

    for (Value idx = 0; idx < allCounters.size(); ++idx) {
        const Counter& counter = allCounters[idx];

        bool valid = true;
        for (int iSol = 0; iSol < nSol; ++iSol) {
            if (solCounters[iSol].collides(counter)) {
                valid = false;
                break;
            }
        }

        if (valid) {
            solCounters.push_back(counter);
            ++nSol;
        }
    }

    std::cout << "result: " << nSol << std::endl;

    return 0;
}
Reto Koradi
sumber
Saya punya solusi rekursif berdasarkan kode yang sama yang dijamin untuk menemukan yang maksimal. Tapi itu n=4sudah cukup lama. Mungkin selesai n=5dengan kesabaran. Anda harus menggunakan strategi backtracking yang lebih baik bahkan untuk membuatnya n=7. Apakah Anda heuristik, atau apakah itu menjelajahi seluruh ruang solusi? Saya merenungkan beberapa ide tentang bagaimana membuat ini lebih baik, baik dengan menyesuaikan urutan penyortiran, atau dengan mungkin tidak murni rakus.
Reto Koradi
Pemahaman saya adalah bahwa tidak ada kemunduran dalam jawaban Peter Taylor. Ini murni serakah. Trik utamanya adalah dia mengurangi jumlah array penghitungan 3^ndan dua heuristik yang dia gambarkan.
@Lembik Komentar saya sebagai tanggapan terhadap komentar yang dihapus. Jumlah array penghitungan harus sama, karena saya membangunnya berdasarkan nilai aktual, dan menguranginya menjadi hanya yang unik. Saya memperbarui versi algoritma penelusuran. Meskipun tidak berakhir dalam waktu yang wajar, ia menemukan 76 dengan n=7cepat. Tapi mencobanya n=9, masih macet di 164 ketika saya menghentikannya setelah 20 menit. Jadi memperpanjang ini dengan bentuk backtracking sederhana tidak terlihat menjanjikan secara umum.
Reto Koradi