고차원 격자 형 그래프에서 가장 큰 독립 집합 찾기


16

주어진 양의 정수 n에 대해서는 길이가 모든 이진 문자열을 고려하십시오 2n-1. 주어진 문자열 에 대해 length 의 각 하위 문자열에있는 s 의 개수를 포함하는 length 배열을 S보자 . 예를 들어 if and then . 우리는 전화 의 계수 배열을Ln1nSn=3S = 01010L=[1,2,1]LS .

우리는 두 개의 문자열 말 S1S2같은 길이의 일치 각각의 계수 배열의 경우 L1와는 L2그 속성이 L1[i] <= 2*L2[i]하고 L2[i] <= 2*L1[i]모두 i.

직무

n에서 시작을 늘리 n=1려면 각 길이의 가장 큰 문자열 세트의 크기를 찾는 것이 작업입니다.2n-1 두 문자열이 일치하지 않도록 .

코드는의 값당 하나의 숫자를 출력해야합니다 n.

점수

귀하의 n답변에 대해 다른 사람이 더 높은 정답을 올리지 않은 점수가 가장 높습니다. 당신은 모든 최적의 답변을 가지고 있다면 분명히 당신은 n당신이 게시 한 최고 점수를 얻을 것이다 . 그러나 귀하의 답변이 최적이 아니더라도 다른 사람이 이길 수 없다면 여전히 점수를 얻을 수 있습니다.

답변 예

들어 n=1,2,3,4내가 얻을 2,4,10,16.

언어와 라이브러리

원하는 언어와 라이브러리를 사용할 수 있습니다. 가능하다면 코드를 실행하는 것이 좋을 것이므로 가능한 경우 리눅스에서 코드를 실행 / 컴파일하는 방법에 대한 자세한 설명을 포함하십시오.

주요 항목

  • Mathematica 에서 Martin Büttner의 5
  • (6) 에 의해 레토 Koradi C ++ . 값은 2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086입니다. 처음 5는 최적 인 것으로 알려져 있습니다.
  • 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

각각의 n에 대해,이 Java 코드는 가능한 카운팅 배열을 결정한 다음, 임의의 세트로 시작하여 랜덤 한 가파른 하강에 의해이를 개선하는 각 크기에 대해 크기가 증가하는 일치하지 않는 세트를 찾습니다. 각 단계에서, 세트의 요소들 중 하나는 무작위로 균일하게 선택되고 사용되지 않는 요소들 중에서 무작위로 선택된 다른 계수 배열로 대체된다. 일치하는 횟수를 늘리지 않으면 단계가 승인됩니다. 이 후자의 처방은 결정적인 것으로 보인다. 일치 횟수를 줄인 경우에만 단계를 수락하는 것이 효과적이지 않습니다. 일치하는 수를 변하지 않는 단계는 검색 공간을 탐색 할 수있게하며, 결과적으로 일치하는 항목 중 하나를 피하기 위해 일부 공간이 열릴 수 있습니다. 개선없이 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가능성 만 있다는 것을 알 수 있으며 첫 번째 차이에서 0 번째 차이의 요소가 0또는 보다 큼 n.

그런 다음 최대 독립 세트를 찾고 싶습니다. 하나의 정리와 두 가지 휴리스틱을 사용하고 있습니다.

  • 정리 : 독립된 그래프 집합의 최대 독립 집합은 최대 독립 집합의 합입니다. 따라서 그래프를 연결되지 않은 구성 요소로 분류하면 문제를 단순화 할 수 있습니다.
  • 휴리스틱 : (n, n, ..., n)최대 독립 세트에 있다고 가정합니다 . 일치하는 가장 작은 정수인 정점의 상당히 큰 {m, m+1, ..., n}^n부분 m이 있습니다 n.(n, n, ..., n)해당 파편 밖에서는 일치하는 것이 없습니다.
  • 휴리스틱 : 최저 정점을 선택하는 욕심 많은 접근 방식을 취하십시오.

이 발견 내 컴퓨터 111에 대한 n=816초에 166대한 n=98에 대한 분, 235대한 n=102에 대한 시간.

암호

다른 이름으로 저장하고 다른 이름 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

보너스로,이 코드는 각 모서리가 두 개의 일치하는 문자열을 나타내는 그래프로 문제의 시각화를 생성합니다.

그래프는 다음과 같습니다 n = 3.

여기에 이미지 설명을 입력하십시오


2
처음에는 그래프가 대칭이라고 생각했지만 왼쪽에 약간의 오프셋 지점이 있습니다. 보이지 않음 :(
orlp

3

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

이것은 Peter Taylor의 결과보다 약간 뒤에 있으며 n=7, 9및 의 경우 1에서 3까지 낮습니다 10. 장점은 속도가 훨씬 빠르므로 더 높은 값으로 실행할 수 있다는 것입니다 n. 그리고 그것은 멋진 수학없이 이해할 수 있습니다. ;)

현재 코드는까지 실행되도록 치수가 설정되어 있습니다 n=15. 실행 시간은 증가 할 때마다 대략 4 배씩 증가 n합니다. 예를 들어 0.008 n=7초, 0.07 초 n=9, 1.34 초 n=11, 27 초 n=13, 9 분입니다 n=15.

내가 사용한 두 가지 주요 관측치가 있습니다.

  • 휴리스틱은 값 자체를 조작하는 대신 계수 배열을 조작합니다. 이를 위해 모든 고유 한 계수 배열 목록이 먼저 생성됩니다.
  • 값이 작은 계수 배열을 사용하면 솔루션 공간이 줄어들 기 때문에 유리합니다. 이는 각각의 수에 기초 c를 제외한 범위 c / 2로를 2 * 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. 휴리스틱입니까? 아니면 전체 솔루션 공간을 탐색 했습니까? 나는 정렬 순서를 미세 조정하거나 순전히 탐욕스럽지 않아서 이것을 개선하는 방법에 대한 아이디어를 생각하고 있습니다.
Reto Koradi

Peter Taylor의 대답에는 역 추적이 없다는 것을 이해합니다. 순전히 탐욕 스럽다. 주요 요점은 계산 배열의 수 3^n와 그가 설명하는 두 가지 휴리스틱을 줄이는 것 입니다.

@Lembik 내 댓글은 삭제 된 댓글에 대한 답변입니다. 실제 값을 기반으로 빌드하고 고유 한 값으로 줄이므로 계산 배열의 수는 같아야합니다. 역 추적 버전의 알고리즘을 업데이트했습니다. 합리적인 시간 내에 종료되지 않더라도 76을 n=7빠르게 찾습니다 . 그러나 시도해 n=9보았지만 20 분 후에 멈추었을 때 여전히 164에 붙어있었습니다. 따라서 제한된 형태의 간단한 역 추적으로 이것을 확장하는 것은 일반적으로 유망하지 않습니다.
레토 코라디
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.