Trova l'insieme indipendente più grande in un grafico simile a un reticolo ad alta dimensione


16

Per un dato intero positivo n, considera tutte le stringhe binarie di lunghezza 2n-1. Per una data stringa S, Lsia un array di lunghezza nche contenga il conteggio del numero di 1s in ciascuna sottostringa di lunghezza ndi S. Ad esempio, se n=3e S = 01010poi L=[1,2,1]. Chiamiamo Lla matrice di conteggio di S.

Diciamo che due stringhe S1e S2della stessa lunghezza corrispondono se i loro rispettivi array di conteggio L1e L2hanno la proprietà che L1[i] <= 2*L2[i]e L2[i] <= 2*L1[i]per tutti i.

Compito

Per aumentare a npartire da n=1, il compito è quello di trovare la dimensione del set più grande di stringhe, ognuna di lunghezza in 2n-1modo che nessuna stringa corrisponda.

Il tuo codice dovrebbe generare un numero per valore di n.

Punto

Il tuo punteggio è il più alto nper il quale nessun altro ha pubblicato una risposta corretta più alta per nessuna delle tue risposte. Chiaramente se hai tutte le risposte ottimali otterrai il punteggio per il punteggio più alto nche pubblichi . Tuttavia, anche se la tua risposta non è ottimale, puoi comunque ottenere il punteggio se nessun altro può batterlo.

Risposte di esempio

Per n=1,2,3,4me capisco 2,4,10,16.

Lingue e biblioteche

Puoi utilizzare qualsiasi lingua e libreria disponibili. Laddove possibile, sarebbe bene poter eseguire il codice, quindi si prega di includere una spiegazione completa su come eseguire / compilare il codice in Linux, se possibile.

Entrate principali

  • 5 di Martin Büttner in Mathematica
  • 6 di Reto Koradi in C ++ . I valori sono 2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086. I primi 5 sono noti per essere ottimali.
  • 7 di Peter Taylor in Java . I valori sono 2, 4, 10, 16, 31, 47, 76, 111, 166, 235.
  • 9 di joriki in Java . I valori sono 2, 4, 10, 16, 31, 47, 76, 112, 168.

3
Penso che sia più naturale capire la disuguaglianza quando annotato come L1[i]/2 <= L2[i] <= 2*L1[i].
Orlp,

1
Inoltre, la corrispondenza non è una relazione di equivalenza. match(A, B)e match(B, C)non implica match(A, C)(lo stesso per l'inverso). Esempio: [1] e [2] corrispondono, [2] e [3] corrispondono, ma [1] e [3] no. Allo stesso modo, [1,3] e [3,1] non corrispondono, [3, 1] e [2, 3] non corrispondono, ma [1, 3] e [2, 3] corrispondono.
orlp

Risposte:


7

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

Per ogni n, questo codice Java determina i possibili array di conteggio e quindi trova insiemi non corrispondenti di dimensioni crescenti, per ogni dimensione che inizia con un insieme casuale e lo migliora con una discesa ripida randomizzata. In ogni passaggio, uno degli elementi dell'insieme viene selezionato casualmente in modo uniforme e sostituito da un altro array di conteggio selezionato casualmente tra quelli non in uso. Il passaggio è accettato se non aumenta il numero di corrispondenze. Quest'ultima prescrizione sembra essere cruciale; accettare i passaggi solo se riducono il numero di partite non è altrettanto efficace. I passaggi che lasciano invariante il numero di corrispondenze consentono di esplorare lo spazio di ricerca e alla fine un po 'di spazio potrebbe aprirsi per evitare una delle corrispondenze. Dopo 2 ^ 24 passaggi senza miglioramento, la dimensione precedente viene emessa per il valore attuale di n e viene incrementato n.

I risultati fino a n = 9 sono 2, 4, 10, 16, 31, 47, 76, 112, 168, migliorando i risultati precedenti per n = 8 di uno e per n = 9 di due. Per valori più alti di n, potrebbe essere necessario aumentare il limite di 2 ^ 24 passi.

Ho anche provato la ricottura simulata (con il numero di partite come energia), ma senza miglioramenti sulla discesa più ripida.

Codice

salva come Question54354.java
compilare con 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));
        }
    }
}

1
Un bel miglioramento!

11

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

Appunti

Se consideriamo un grafico Gcon vertici 0a ne bordi di giunzione di due numeri che corrispondenza l' alimentazione tensore G^n ha vertici (x_0, ..., x_{n-1})che formano la potenza cartesiano {0, ..., n}^ne spigoli tra tuple corrispondente. Il grafico di interesse è il sottografo G^n indotto da quei vertici che corrispondono ai possibili "array di conteggio".

Quindi la prima sottoattività è quella di generare quei vertici. L'approccio ingenuo enumera le 2^{2n-1}stringhe o nell'ordine di 4^n. Ma se invece osserviamo la matrice delle prime differenze delle matrici di conteggio scopriamo che ci sono solo 3^npossibilità e dalle prime differenze possiamo dedurre l'intervallo dei possibili valori iniziali richiedendo che nessun elemento nelle differenze di zeroth sia inferiore 0o maggiore di n.

Vogliamo quindi trovare il set massimo indipendente. Sto usando un teorema e due euristiche:

  • Teorema: l'insieme indipendente massimo di un'unione disgiunta di grafici è l'unione dei loro insiemi indipendenti massimi. Quindi, se suddividiamo un grafico in componenti non collegati, possiamo semplificare il problema.
  • Euristico: supponiamo che (n, n, ..., n)sarà in un set massimo indipendente. C'è una cricca abbastanza grande di vertici in {m, m+1, ..., n}^ncui mè il numero intero più piccolo che corrisponde n; (n, n, ..., n)è garantito che non ci siano partite al di fuori di quella cricca.
  • Euristico: adottare l'approccio avido di selezionare il vertice di grado più basso.

Sul mio computer questo si trova 111per n=816 secondi, 166per n=9circa 8 minuti e 235per n=10circa 2 ore.

Codice

Salva come PPCG54354.java, compila come javac PPCG54354.javaed esegui come 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();
        }
    }
}

10

Mathematica n = 5,, 31 stringhe

Ho appena scritto una soluzione per la forza bruta utilizzando gli incorporati di Mathematica per verificare le risposte di esempio di Lembik, ma può anche gestire n = 5:

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

Come bonus, questo codice produce una visualizzazione del problema sotto forma di grafico in cui ciascun bordo indica due stringhe corrispondenti.

Ecco il grafico per n = 3:

inserisci qui la descrizione dell'immagine


2
All'inizio ho pensato che il grafico fosse ben simmetrico, ma poi ho visto il punto leggermente sfalsato sulla sinistra. Non riesco a vedere :(
orlp

3

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

Questo è leggermente dietro il risultato di Peter Taylor, essendo 1 a 3 inferiore per n=7, 9e 10. Il vantaggio è che è molto più veloce, quindi posso eseguirlo per valori più alti di n. E può essere compreso senza alcuna fantasia matematica. ;)

Il codice corrente è dimensionato per l'esecuzione n=15. I tempi di esecuzione aumentano approssimativamente di un fattore 4 per ogni aumento di n. Ad esempio, era 0,008 secondi per n=7, 0,07 secondi per n=9, 1,34 secondi per n=11, 27 secondi per n=13e 9 minuti per n=15.

Ci sono due osservazioni chiave che ho usato:

  • Invece di operare sui valori stessi, l'euristica opera sul conteggio delle matrici. Per fare ciò, viene generato prima un elenco di tutti gli array di conteggio unici.
  • L'uso di array di conteggio con valori piccoli è più vantaggioso, poiché eliminano meno spazio della soluzione. Questo si basa su ogni conteggio cescludendo la gamma di c / 2a 2 * cda altri valori. Per valori più piccoli di c, questo intervallo è più piccolo, il che significa che vengono utilizzati meno valori.

Genera array di conteggio unici

Sono andato su questa forza bruta, ripetendo tutti i valori, generando l'array di conteggio per ciascuno di essi e unificando l'elenco risultante. Questo potrebbe certamente essere fatto in modo più efficiente, ma è abbastanza buono per i tipi di valori con cui operiamo.

Questo è estremamente veloce per i piccoli valori. Per i valori più grandi, il sovraccarico diventa sostanziale. Ad esempio, per n=15, utilizza circa il 75% dell'intero tempo di esecuzione. Questa sarebbe sicuramente un'area da guardare quando si cerca di andare molto più in alto n=15. Anche solo l'uso della memoria per la creazione di un elenco degli array di conteggio per tutti i valori inizierebbe a diventare problematico.

Il numero di matrici di conteggio univoche è circa il 6% del numero di valori per n=15. Questo conteggio relativo diventa più piccolo man mano che naumenta.

Selezione avida di valori di array di conteggio

La parte principale dell'algoritmo seleziona il conteggio dei valori di array dall'elenco generato usando un semplice approccio avido.

Sulla base della teoria secondo cui è utile utilizzare gli array di conteggio con piccoli conteggi, gli array di conteggio sono ordinati in base alla somma dei loro conteggi.

Vengono quindi controllati in ordine e viene selezionato un valore se è compatibile con tutti i valori utilizzati in precedenza. Quindi questo comporta un singolo passaggio lineare attraverso le matrici di conteggio univoche, in cui ciascun candidato viene confrontato con i valori precedentemente selezionati.

Ho alcune idee su come l'euristica potrebbe essere potenzialmente migliorata. Ma questo sembrava un ragionevole punto di partenza e i risultati sembravano abbastanza buoni.

Codice

Questo non è altamente ottimizzato. Avevo una struttura di dati più elaborata ad un certo punto, ma avrebbe avuto bisogno di più lavoro per generalizzarla oltre n=8, e la differenza nelle prestazioni non sembrava molto sostanziale.

#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;
}

Ho avuto soluzioni ricorsive basate su codice simile che sono garantite per trovare il massimo. Ma ci è voluto un po 'di tempo n=4. Potrebbe essere finito n=5con un po 'di pazienza. Devi aver usato una migliore strategia di backtracking anche per farcela n=7. Il tuo era euristico o ha esplorato l'intero spazio della soluzione? Sto contemplando alcune idee su come renderlo migliore, sia perfezionando l'ordinamento, sia forse non essendo puramente avido.
Reto Koradi,

La mia comprensione è che non vi è alcun backtracking nella risposta di Peter Taylor. È puramente avido. Il trucco principale è che riduce il numero di matrici di conteggio 3^ne le due euristiche che descrive.

@Lembik Il mio commento è stato in risposta a un commento che è stato eliminato. Il numero di array di conteggio dovrebbe essere lo stesso, poiché lo costruisco in base a valori reali e lo riduco a quelli unici. Ho aggiornato una versione di backtracking dell'algoritmo. Anche se non termina entro un tempo ragionevole, trova n=7rapidamente 76 . Ma provandolo n=9, era ancora bloccato a 164 quando l'ho fermato dopo 20 minuti. Quindi estenderlo con una forma limitata di backtracking non sembra generalmente promettente.
Reto Koradi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.