高次元の格子状グラフで最大の独立集合を見つける


16

指定された正の整数について、n長さのすべてのバイナリ文字列を考慮します2n-1。与えられた文字列について、の長さの各部分文字列内のs の数のカウントを含む長さの配列をしSましょう。たとえば、if およびthen です。のカウント配列を呼び出します。Ln1nSn=3S = 01010L=[1,2,1]LS

同じカウントの2つの文字列が一致し、それぞれのカウント配列がプロパティthat およびfor all を持っている場合に一致するS1と言います。S2L1L2L1[i] <= 2*L2[i]L2[i] <= 2*L1[i]i

仕事

増加のためnで始まるn=1、タスクは、長さの各文字列の最大セットのサイズを見つけることです2n-1何の2つの文字列が一致しないように。

コードは、値ごとに1つの数値を出力する必要がありますn

スコア

あなたのスコアは、n他の誰もあなたの答えのいずれに対してもより高い正解を投稿していない最高です。明らかに、すべての最適な回答があれば、n投稿した最高のスコアが得られます。ただし、あなたの答えが最適でなくても、他の誰もそれを打つことができなければ、あなたはまだスコアを得ることができます。

回答例

以下のためにn=1,2,3,4私が取得します2,4,10,16

言語とライブラリ

使用可能な任意の言語とライブラリを使用できます。可能であれば、コードを実行できるとよいので、可能であればLinuxでコードを実行/コンパイルする方法の完全な説明を含めてください。

主要なエントリー

  • 5 Mathematicaの MartinBüttnerによる
  • C ++の Reto Koradiによる6。値は2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086です。最初の5つは最適であることが知られています。
  • Javaの Peter Taylorによる7。値は 2, 4, 10, 16, 31, 47, 76, 111, 166, 235です。
  • Javaの jorikiによる9。値は2, 4, 10, 16, 31, 47, 76, 112, 168です。

3
として表記されてL1[i]/2 <= L2[i] <= 2*L1[i]いる場合、不平等を理解する方が自然だと思います。
orlp

1
また、一致は等価関係ではありませんmatch(A, B)そして、match(B, C)意味するものではありませんmatch(A, C)(逆も同じ)。例:[1]と[2]は一致し、[2]と[3]は一致しますが、[1]と[3]は一致しません。同様に、[1,3]と[3,1]は一致しません。[3、1]と[2、3]は一致しませんが、[1、3]と[2、3]は一致します。
orlp

回答:


7

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

このJavaコードは、nごとに、可能なカウント配列を決定し、ランダムセットから開始し、ランダム化された最急降下によってサイズを改善する、サイズが増加する不一致セットを見つけます。各ステップで、セットの要素の1つがランダムに均一に選択され、使用されていない要素の中からランダムに均一に選択された別のカウント配列に置き換えられます。一致の数が増えない場合、ステップは受け入れられます。この後者の処方は重要であると思われます。一致の数を減らす場合にのみステップを受け入れることは、それほど効果的ではありません。不一致の一致数を残すステップにより、検索スペースを探索することができ、最終的に一致の1つを回避するために一部のスペースが開かれる場合があります。2 ^ 24ステップの改善なしで、nの現在の値に対して以前のサイズが出力され、nが増分されます。

n = 9 2, 4, 10, 16, 31, 47, 76, 112, 168までの結果は、n = 8が1で、n = 9が2で、前の結果を改善しています。nの値が大きい場合、2 ^ 24ステップの制限を増やす必要があります。

また、シミュレートされたアニーリング(エネルギーとして一致の数を使用)を試みましたが、最急降下に対する改善はありませんでした。

コード

実行時にQuestion54354.java
コンパイルとして保存javac Question54354.java
java 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
非常に素晴らしい改善です!

11

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

ノート

我々はグラフを考慮すればG、頂点と0n二つの数字が一致する接合縁は、テンソル力が G^n頂点有する(x_0, ..., x_{n-1})デカルト動力形成{0, ..., n}^nマッチングタプルの間のエッジを。対象のグラフは、可能な「カウント配列」に対応する頂点G^n によって誘導されるサブグラフです。

したがって、最初のサブタスクは、これらの頂点を生成することです。ナイーブアプローチは、2^{2n-1}文字列またはの順序で列挙します4^n。私たちが代わりにカウント配列の最初の相違点の配列を見ればしかし、我々は唯一の存在であることを見つける3^n可能性があり、第1の違いから、我々はゼロ番目の違いでどの要素が未満でないことを要求することにより可能初期値の範囲を推定することができます0かより大きいn

次に、最大の独立集合を見つけます。1つの定理と2つのヒューリスティックを使用しています。

  • 定理:グラフの互いに素な結合の最大独立集合は、それらの最大独立集合の結合です。したがって、グラフを接続されていないコンポーネントに分解すると、問題を単純化できます。
  • ヒューリスティック:(n, n, ..., n)最大独立セットにあると仮定します。頂点のかなり大きなクリークあります一致する最小の整数であるが、そのクリークの外側に一致するものがないことが保証されます。{m, m+1, ..., n}^nmn(n, n, ..., n)
  • ヒューリスティック:最も低い次数の頂点を選択する貪欲なアプローチを取ります。

私のコンピュータで、この発見111のためのn=816秒で、166ためにn=9約8分で、と235のためn=10で約2時間。

コード

名前を付けて保存しPPCG54354.java、名前を付けてコンパイルし、名前を付けjavac PPCG54354.javaて実行し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 = 531文字列

Mathematicaのビルトインを使用してブレンフォースソリューションを作成し、Lembikのサンプルの回答を検証しましたが、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

ボーナスとして、このコードは、各エッジが2つの一致する文字列を示すグラフとして問題を視覚化します。

以下がグラフですn = 3

ここに画像の説明を入力してください


2
最初はグラフがきれいに対称であると思っていましたが、左にわずかにずれた点が見えました。見えない:(
orlp

3

C ++(ヒューリスティック):2、4、10、16、31、47、75、111、164、232、328、445、606、814、1086

これは、1〜3低いこと、ピーター・テイラーの結果の後ろにわずかであるn=7910。利点は、はるかに高速であるため、より高い値で実行できることですn。そして、それは派手な数学なしで理解することができます。;)

現在のコードのサイズは、最大で実行されn=15ます。実行時間は、の増加ごとに約4倍増加しnます。たとえばn=7、0.08秒、0.07秒n=9、1.34秒n=11、27秒n=13、9分でしたn=15

私が使用した2つの重要な観察結果があります。

  • 値自体を操作する代わりに、ヒューリスティックはカウント配列を操作します。これを行うには、最初にすべての一意のカウント配列のリストが生成されます。
  • 小さい値でカウント配列を使用すると、解決するスペースが少なくなるため、より有益です。これは、各カウントに基づいているcの範囲を除くc / 22 * c、他の値から。の値が小さい場合c、この範囲は小さくなります。つまり、除外される値が少なくなります。

一意のカウント配列を生成

私はこれに強引に取り組み、すべての値を繰り返し処理し、それぞれの値の配列を生成し、結果のリストを一意にしました。これは確かに効率的に実行できますが、操作する種類の値には十分です。

これは、小さな値に対して非常に高速です。大きな値の場合、オーバーヘッドはかなり大きくなります。たとえば、の場合、n=15ランタイム全体の約75%を使用します。これは間違いなくはるかに高く移動しようとするときに見るべきエリアですn=15。すべての値のカウント配列のリストを作成するためのメモリ使用量だけでも問題になり始めます。

一意のカウント配列の数は、の値の数の約6%ですn=15。この相対カウントは、n大きくなるにつれて小さくなります。

配列値のカウントの貪欲な選択

アルゴリズムの主要部分は、単純な貪欲なアプローチを使用して、生成されたリストから配列値のカウントを選択します。

カウント数の少ないカウント配列を使用すると有益であるという理論に基づいて、カウント配列はカウントの合計でソートされます。

これらは順番にチェックされ、以前に使用されたすべての値と互換性がある場合は値が選択されます。そのため、これには一意のカウント配列を通る単一の線形パスが含まれ、各候補が以前に選択された値と比較されます。

ヒューリスティックをどのように改善できるかについて、いくつかのアイデアがあります。しかし、これは合理的な出発点のようであり、結果は非常に良さそうでした。

コード

これは高度に最適化されていません。ある時点でより複雑なデータ構造がありましたがn=8、それを超えて一般化するにはより多くの作業が必要であり、パフォーマンスの違いはそれほど大きくはありませんでした。

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

最大値を見つけることが保証されている同様のコードに基づいた再帰的なソリューションがありました。しかし、n=4もうしばらく時間がかかりました。n=5多少の忍耐で終わったかもしれません。あなたはそれを達成するために、より良いバックトラッキング戦略を使用したに違いありませんn=7。あなたの発見的発見でしたか、それともソリ​​ューション空間全体を探索しましたか?私は、ソート順序を微調整するか、純粋に欲張りにならないようにすることで、これを改善する方法についていくつかのアイデアを考えています。
レトコラディ

私の理解では、ピーター・テイラーの答えには後戻りがないということです。それは純粋に貪欲です。主なトリックは、彼がカウント配列の数を減らすこと3^nと、彼が説明する2つのヒューリスティックです。

@Lembik削除されたコメントに対する私のコメント。カウント配列の数は、実際の値に基づいて作成し、一意の配列のみに減らすため、同じにする必要があります。アルゴリズムのバックトラッキングバージョンを更新しました。妥当な時間内に終了しなくても、76をn=7すばやく見つけます。しかしn=9、それを試してみましたが、20分後に停止したときに164のままでした。したがって、限られた形式の単純なバックトラッキングでこれを拡張しても、一般的には有望に見えません。
レトコラディ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.