在高维格子状图中找到最大的独立集合


16

对于给定的正整数n,请考虑所有长度为二进制的字符串2n-1。对于给定的字符串S,令其L为一个长度数组,n其中包含1每个长度n为的子字符串中s 的数量S。例如,如果n=3S = 01010L=[1,2,1]。我们称L的计数数组S

我们说两个字符串S1S2相同长度的比赛,如果他们各自的计数阵列L1L2具有这样的性质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

语言和图书馆

您可以使用任何喜欢的语言和库。在可行的情况下,能够运行您的代码将是一件好事,因此,请尽可能提供有关如何在Linux中运行/编译代码的完整说明。

领先的作品

  • 5由MartinBüttner在Mathematica中撰写
  • 6由Reto Koradi用C ++编写。值是2, 4, 10, 16, 31, 47, 75, 111, 164, 232, 328, 445, 606, 814, 1086。已知前5个是最佳的。
  • 7由Peter Taylor在Java撰写。值是 2, 4, 10, 16, 31, 47, 76, 111, 166, 235
  • 9由joriki用Java编写。值是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 2015年

Answers:


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的结果和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或等于,从一阶差推导出可能的初始值的范围。大于n

然后,我们想找到最大的独立集。我正在使用一个定理和两种启发式方法:

  • 定理:图的不相交联合的最大独立集是其最大独立集的并集。因此,如果我们将图形分解为未连接的组件,则可以简化问题。
  • 启发式:假定(n, n, ..., n)将在最大的独立集合中。有相当多的顶点团,{m, m+1, ..., n}^n其中m最小的整数匹配n(n, n, ..., n)保证在该派系之外没有任何比赛。
  • 启发式:采用贪婪的方法来选择最低度的顶点。

在我的电脑这个认定111n=816秒,166n=98分钟左右,并235n=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 = 5,,31弦

我刚刚使用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
起初我以为图形很好地对称,但是后来我看到了左侧的稍微偏移的点。看
不清

3

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

这比彼得·泰勒(Peter Taylor)的结果稍稍落后n=79并且和分别降低了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 / 22 * c其他值 to。对于的较小值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 2015年

我的理解是,彼得·泰勒(Peter Taylor)的答案没有回溯。纯粹是贪婪。主要技巧是他将计数数组的数量减少到3^n他描述的两种启发式方法。

@Lembik我的评论是对一条评论被删除的回应。计数数组的数量应该相同,因为我是根据实际值构建的,并将其减少为唯一的。我更新了算法的回溯版本。即使它没有在合理的时间内终止,它也会n=7迅速找到76 。但是尝试一下时n=9,当我在20分钟后将其停止时,它仍然停留在164。因此,以有限形式的简单回溯来扩展它似乎并不大有希望。
Reto Koradi 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.