Finden Sie die größte unabhängige Menge in einem hochdimensionalen gitterartigen Graphen


16

nBerücksichtigen Sie für eine bestimmte positive Ganzzahl alle binären Zeichenfolgen mit der Länge 2n-1. SSei für einen gegebenen String Lein Array von Länge, ndas die Anzahl der 1s in jedem Teilstring von Länge nvon enthält S. Zum Beispiel, wenn n=3und S = 01010dann L=[1,2,1]. Wir nennen Ldas Zählarray von S.

Wir sagen , dass zwei Strings S1und S2die gleiche Länge Spiel , wenn ihre jeweiligen Zählen Arrays L1und L2haben die Eigenschaft , dass L1[i] <= 2*L2[i]und L2[i] <= 2*L1[i]für alle i.

Aufgabe

Zum Erhöhen nab n=1besteht die Aufgabe darin, die Größe des größten Satzes von Zeichenfolgen zu finden, wobei jede Länge 2n-1so ist, dass keine zwei Zeichenfolgen übereinstimmen.

Ihr Code sollte eine Zahl pro Wert von ausgeben n.

Ergebnis

Ihre Punktzahl ist die höchste, nfür die noch niemand eine richtigere Antwort für eine Ihrer Antworten abgegeben hat. Wenn Sie alle optimalen Antworten haben, erhalten Sie die Punktzahl für den höchsten nBeitrag, den Sie verfassen. Auch wenn Ihre Antwort nicht optimal ist, können Sie die Punktzahl erhalten, wenn niemand anders sie schlagen kann.

Beispielantworten

Denn n=1,2,3,4ich verstehe 2,4,10,16.

Sprachen und Bibliotheken

Sie können jede verfügbare Sprache und Bibliothek verwenden, die Sie mögen. Wo immer möglich, wäre es gut, wenn Sie Ihren Code ausführen könnten. Fügen Sie daher bitte eine vollständige Erklärung dazu bei, wie Sie Ihren Code unter Linux ausführen / kompilieren, wenn dies überhaupt möglich ist.

Führende Einträge

  • 5 von Martin Büttner in Mathematica
  • 6 von Reto Koradi in C ++ . Werte sind 2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086. Die ersten 5 sind als optimal bekannt.
  • 7 von Peter Taylor in Java . Werte sind 2, 4, 10, 16, 31, 47, 76, 111, 166, 235.
  • 9 von joriki in Java . Werte sind 2, 4, 10, 16, 31, 47, 76, 112, 168.

3
Ich denke, es ist natürlicher, die Ungleichung zu verstehen, wenn man sie als notiert L1[i]/2 <= L2[i] <= 2*L1[i].
Orlp

1
Matching ist auch keine Äquivalenzbeziehung. match(A, B)und match(B, C)impliziert nicht match(A, C)(dasselbe für die Umkehrung). Beispiel: [1] und [2] stimmen überein, [2] und [3] stimmen überein, [1] und [3] jedoch nicht. Ebenso stimmen [1,3] und [3,1] nicht überein, [3, 1] und [2, 3] stimmen nicht überein, aber [1, 3] und [2, 3] stimmen überein.
Orlp

Antworten:


7

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

Für jedes n bestimmt dieser Java-Code die möglichen Zählarrays und findet dann nicht übereinstimmende Mengen mit zunehmender Größe, wobei jede Größe mit einer zufälligen Menge beginnt und durch zufällige steilste Abnahme verbessert wird. In jedem Schritt wird eines der Elemente des Satzes zufällig gleichmäßig ausgewählt und durch ein anderes Zählarray ersetzt, das zufällig gleichmäßig aus den nicht verwendeten ausgewählt wird. Der Schritt wird akzeptiert, wenn die Anzahl der Übereinstimmungen nicht erhöht wird. Diese letztere Vorschrift scheint entscheidend zu sein; Schritte nur dann zu akzeptieren, wenn sie die Anzahl der Übereinstimmungen verringern, ist bei weitem nicht so effektiv. Die Schritte, bei denen die Anzahl der Übereinstimmungen unverändert bleibt, ermöglichen das Durchsuchen des Suchraums. Schließlich kann sich etwas Platz öffnen, um eine der Übereinstimmungen zu vermeiden. Nach 2 ^ 24 Schritten ohne Verbesserung wird die vorherige Größe für den gegenwärtigen Wert von n ausgegeben und n wird inkrementiert.

Die Ergebnisse bis n = 9 2, 4, 10, 16, 31, 47, 76, 112, 168verbessern sich gegenüber den vorherigen Ergebnissen für n = 8 um eins und für n = 9 um zwei. Für höhere Werte von n muss die Grenze von 2 ^ 24 Schritten möglicherweise erhöht werden.

Ich habe auch simuliertes Tempern versucht (mit der Anzahl der Übereinstimmungen als Energie), aber ohne Verbesserung gegenüber der steilsten Abfahrt.

Code

Speichern als Question54354.java
Kompilieren mit javac Question54354.java
Ausführen mitjava 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
Eine sehr schöne Verbesserung!

11

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

Anmerkungen

Wenn wir einen Graphen Gmit Eckpunkten 0zu nund Kanten betrachten, die zwei übereinstimmende Zahlen verbinden, dann hat die Tensor-Potenz G^n Eckpunkte, (x_0, ..., x_{n-1})die die kartesische Potenz {0, ..., n}^nund Kanten zwischen übereinstimmenden Tupeln bilden. Der Graph von Interesse ist der Untergraph vonG^n induziert durch jene Eckpunkte, die den möglichen "Zählarrays" entsprechen.

Die erste Teilaufgabe besteht also darin, diese Eckpunkte zu generieren. Der naive Ansatz zählt über 2^{2n-1}Strings oder in der Reihenfolge von auf 4^n. Betrachten wir stattdessen das Array der ersten Unterschiede der Zählarrays, so gibt es nur 3^nMöglichkeiten. Aus den ersten Unterschieden können wir den Bereich der möglichen Anfangswerte ableiten, indem wir voraussetzen, dass kein Element in den nullten Unterschieden kleiner als ist0 oder ist größer als n.

Wir wollen dann die maximale unabhängige Menge finden. Ich benutze einen Satz und zwei Heuristiken:

  • Satz: Die maximale unabhängige Menge einer disjunkten Vereinigung von Graphen ist die Vereinigung ihrer maximalen unabhängigen Mengen. Wenn wir also ein Diagramm in nicht verbundene Komponenten aufteilen, können wir das Problem vereinfachen.
  • Heuristik: Nehmen Sie an, dass (n, n, ..., n)es sich um eine maximale unabhängige Menge handelt. Es gibt eine ziemlich große Gruppe von Eckpunkten, bei {m, m+1, ..., n}^ndenen mdie kleinste Ganzzahl übereinstimmt n.(n, n, ..., n)garantiert keine Übereinstimmungen außerhalb dieser Clique.
  • Heuristik: Verwenden Sie den gierigen Ansatz, den Scheitelpunkt mit dem niedrigsten Grad auszuwählen.

Auf meinem Computer diese Funde 111für n=816 Sekunden 166für n=9in etwa 8 Minuten und 235für n=10in etwa 2 Stunden.

Code

Speichern als PPCG54354.java, Kompilieren als javac PPCG54354.javaund Ausführen als 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 Saiten

Ich schrieb eine Brute - Force - Lösung mit Mathematica eingebauten Ins Lembik des Beispiel Antworten zu überprüfen, aber es kann behandeln n = 5auch:

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

Als Bonus erstellt dieser Code eine grafische Darstellung des Problems, wobei jede Kante zwei übereinstimmende Zeichenfolgen angibt.

Hier ist die Grafik für n = 3:

Bildbeschreibung hier eingeben


2
Zuerst dachte ich, die Grafik sei schön symmetrisch, aber dann sah ich den leicht versetzten Punkt links. Kann nicht sehen :(
Orlp

3

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

Dies ist etwas hinter Peter Taylors Ergebnis zurück, das für und 1 bis 3 niedriger n=7ist . Der Vorteil ist, dass es viel schneller ist, so dass ich es für höhere Werte von ausführen kann . Und es kann ohne ausgefallene Mathematik verstanden werden. ;)910n

Der aktuelle Code ist so dimensioniert, dass er bis zu ausgeführt werden kann n=15. Die Laufzeiten erhöhen sich bei jeder Erhöhung von ungefähr um den Faktor 4 n. Zum Beispiel war sie 0,008 Sekunden lang n=7, 0,07 Sekunden lang n=9, 1,34 Sekunden lang n=11, 27 Sekunden lang n=13und 9 Minuten langn=15 .

Ich habe zwei wichtige Beobachtungen gemacht:

  • Anstatt die Werte selbst zu verarbeiten, werden in der Heuristik Arrays gezählt. Dazu wird zunächst eine Liste aller eindeutigen Zählarrays erstellt.
  • Die Verwendung von Zählarrays mit kleinen Werten ist vorteilhafter, da sie weniger Platz in der Lösung beanspruchen. Dies basiert auf jeder Zählung cohne den Bereich von c / 2bis 2 * cvon anderen Werten. Für kleinere Werte von cist dieser Bereich kleiner, was bedeutet, dass durch die Verwendung weniger Werte ausgeschlossen werden.

Generieren Sie eindeutige Zählarrays

Ich habe mich mit aller Kraft an die Arbeit gemacht, alle Werte durchlaufen, das Zählarray für jeden von ihnen generiert und die resultierende Liste eindeutig festgelegt. Dies könnte sicherlich effizienter erfolgen, ist aber für die Werte, mit denen wir arbeiten, gut genug.

Dies geht bei den kleinen Werten extrem schnell. Für die größeren Werte wird der Overhead erheblich. Zum Beispiel verbraucht n=15es 75% der gesamten Laufzeit. Dies wäre definitiv ein Bereich, den man sich ansehen sollte, wenn man versucht, viel höher zu steigen als n=15. Selbst die Speicherbelegung für die Erstellung einer Liste der Zählarrays für alle Werte würde problematisch.

Die Anzahl der eindeutigen Zählarrays beträgt ungefähr 6% der Anzahl der Werte für n=15. Diese relative Anzahl wird kleiner, wenn sie ngrößer wird.

Gierige Auswahl von Zählarraywerten

Der Hauptteil des Algorithmus wählt das Zählen von Array-Werten aus der generierten Liste unter Verwendung eines einfachen gierigen Ansatzes aus.

Basierend auf der Theorie, dass die Verwendung von Zählarrays mit kleinen Zählwerten vorteilhaft ist, werden die Zählarrays nach der Summe ihrer Zählwerte sortiert.

Sie werden dann der Reihe nach überprüft und ein Wert wird ausgewählt, wenn er mit allen zuvor verwendeten Werten kompatibel ist. Dies umfasst also einen einzelnen linearen Durchlauf durch die eindeutigen Zählarrays, bei dem jeder Kandidat mit den zuvor ausgewählten Werten verglichen wird.

Ich habe einige Ideen, wie die Heuristik möglicherweise verbessert werden könnte. Dies schien jedoch ein vernünftiger Ausgangspunkt zu sein, und die Ergebnisse sahen recht gut aus.

Code

Dies ist nicht sehr optimiert. Irgendwann hatte ich eine ausgefeiltere Datenstruktur, aber es hätte mehr Arbeit gebraucht, um sie darüber hinaus zu verallgemeinern n=8, und der Leistungsunterschied schien nicht sehr erheblich zu sein.

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

Ich hatte rekursive Lösungen basierend auf ähnlichem Code, die garantiert das Maximum finden. Aber es hat schon eine Weile n=4gedauert. Es könnte n=5mit etwas Geduld beendet haben. Sie müssen eine bessere Backtracking-Strategie angewendet haben, um es überhaupt zu schaffen n=7. War Ihre Heuristik oder wurde der gesamte Lösungsraum untersucht? Ich denke über einige Ideen nach, wie ich das verbessern kann, entweder indem ich die Sortierreihenfolge verfeinere oder indem ich nicht nur gierig bin.
Reto Koradi

Ich verstehe, dass Peter Taylors Antwort keine Rückschritte enthält. Es ist rein gierig. Die wichtigsten Tricks sind, dass er die Anzahl der Zählarrays auf 3^nund die beiden von ihm beschriebenen Heuristiken reduziert .

@Lembik Mein Kommentar wurde als Antwort auf einen Kommentar abgegeben, der gelöscht wurde. Die Anzahl der Zähl-Arrays sollte gleich sein, da ich das auf der Basis der tatsächlichen Werte erstelle und es nur auf die eindeutigen reduziere. Ich habe eine Backtracking-Version des Algorithmus aktualisiert. Obwohl es nicht innerhalb einer angemessenen Zeit endet, findet es 76 n=7schnell. Aber n=9als ich es ausprobierte , blieb es immer noch bei 164 stecken, als ich es nach 20 Minuten stoppte. Eine Erweiterung mit einer eingeschränkten Form des einfachen Backtrackings erscheint daher nicht generell vielversprechend.
Reto Koradi
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.