0

SOにはサブセットサムの質問と回答がたくさんありますが、どういうわけか私は特定の問題の解決策を見つけることができません.

長さ n のトラックを作成するには、トラック セグメントの数と長さを見つける必要があります。セグメントの長さ: 8, 10, 12, 14, 16, 18, 20, 22, 24 フィート 数量は最大 4 セグメントです トラックの長さは ~ 20 ~ 100 フィートです (常に偶数)

これは本物のトラックです。セグメントの順序は関係ありません。ただし、好ましいサイズの組み合わせがあります。大きい/小さい組み合わせよりも、すべて同じ長さまたは互いに近い方が優先されます。

すなわち:

  • 70 => 20,20,20,10 は簡単に選択できますが、16,18,18,18 が優先されます
  • 60 => 20,20,20 は 14,14,14,18 よりも優れています

必要に応じて、さらに例を挙げることができます。

私は 1 つの最適なソリューションを探しているのではなく、考えられる最適なソリューションの小さなセットを探しています。最終的には人が選択しますが、これは最良の選択肢の提案に関するものです。

これまでに行ったことは次のとおりです。それは機能していますが、複雑な方法のようです。

この投稿Algorithm to find which numbers from a list of a list of size n sum to another numberから基本的なアルゴリズムを採用しました。このコードで変更したのは、整数に変えることだけです。可能なすべての組み合わせを返します。5曲以上まで。

結果セットをさらに減らすために、いくつかのLinqを実行します

    List<int> nums = new List<int>() { 8, 8, 8, 8, 10, 10, 10, 10, 12, 12, 12, 12, 14, 14, 14, 14, 16, 16, 16, 16, 18, 18, 18, 18, 20, 20, 20, 20, 22, 22, 22, 22, 24, 24, 24, 24 };
    int width = 60;
    Console.WriteLine("Total width: " + width);
    Solver solver = new Solver();
    List<List<int>> res = solver.Solve(width, nums.ToArray());

    Console.WriteLine("total :"+res.Count);
    var res1 = res.Distinct(new SequenceComparer<List<int>, int>()).ToList();
    Console.WriteLine("total res1:" + res1.Count);

    var res2 = res1.Where(l => l.Count == 4).ToList();
    Console.WriteLine("total res2:" + res2.Count); //reduce to 4 integer solutions

    var res3 = (from row in res2
             where row[0] == row[1] || row[0] == row[2] || row[0] == row[3] ||
                   row[1] == row[2] || row[1] == row[3] ||
                   row[2] == row[3]
             select row).ToList();
    Console.WriteLine("total res3:" + res3.Count); //reduce to at least two of equal length

    var res4 = (from row in res3
            where row[0] == row[1] && row[0] == row[2] || 
                  row[0] == row[1] && row[0] == row[3] ||
                  row[1] == row[2] && row[1] == row[3] 
            select row).ToList();
     Console.WriteLine("total res4:" + res4.Count); //reduce to  three of equal length

    var res5 = (from row in res4
            where row[0] == row[1] && row[0] == row[2] && row[0] == row[3]
           select row).ToList();

     Console.WriteLine("total res5:" + res5.Count); //reduce to 4 of equal length
     Console.WriteLine("-------------------------------------");
     Console.WriteLine("res4:");
     foreach (List<int> result in res4)
     {
         foreach (int value in result)
         {
              Console.Write("{0}\t", value);
         }
         Console.WriteLine();
      }
      Console.WriteLine("-------------------------------------");
      Console.WriteLine("res5:");
      foreach (List<int> result in res5)
      {
           foreach (int value in result)
           {
               Console.Write("{0}\t", value);
           }
           Console.WriteLine();
      }
      Console.ReadLine();

このコードは次の結果を生成し、60 で実行します

    Total width: 60
    total :10726
    total res1:74
    total res2:31
    total res3:20
    total res4:3
    total res5:0
    -------------------------------------
    res4:
    12      12      12      24
    12      16      16      16
    14      14      14      18
     -------------------------------------
    res5:

または80でこれ

    Total width: 80
    total :101560
    total res1:237
    total res2:15
    total res3:13
    total res4:3
    total res5:1
    ------------------------------------
    res4:
    8       24      24      24
    14      22      22      22
    20      20      20      20
    ------------------------------------
    res5:
    20      20      20      20

したがって、私の最終結果 (4&5) は、実際には必要なものに近いものです。

しかし、考えられる 3 トラック ソリューション、場合によっては 2 トラックについても、同じコードを再度コーディングする必要があります。

次に、結果を互いに比較する必要がありますか(どういうわけか、方法がわからない)。これらすべてが、何かが足りないような気がします。それは複雑に感じます、それは間違っていると感じます。私は何が欠けていますか?

そもそも間違ったアルゴリズムを使用していますか? 彼らは私の問題を解決するのに優れていますか?

4

3 に答える 3

2

あなたは本当に和集合の特別なケースを持っています。それは NP 困難ですが、ソリューション スペースは十分に制限されているため、ブルート フォースがおそらく正常に機能する可能性があります。また、可能な長さとして 0 を考慮する必要があります。

疑似Pythonのコードは次のとおりです。C# で試してみようと思ったのですが、実際には慣れていないので、うまくいかないでしょう。

lengths = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24]
solutions = []
for len1 in lengths:
    for len2 in lengths:
        for len3 in lengths:
            for len4 in lengths:
                if (len1 + len2 + len3 + len4) == n:
                    solutions.append([len1,len2,len3,len4])
return solutions

すべての有効なソリューションを取得したら、どのソリューションをユーザーに表示するかを決定するか、アルゴリズムを記述して最適なソリューションを選択することができます。もちろん、実際にはサイズ 0 の長さを含めたくないでしょう。

すべての有効なソリューションのみを見つける貪欲な方法を使用して、このアルゴリズムをいくらか改善できます。ただし、繰り返しになりますが、スペースや時間の面で非常に制限されていない限り、問題はそれを必要とするほど複雑ではない可能性があります。

おまけとして、複数のクエリが予想される場合 (たとえば、ユーザーが n = 40 を要求し、後で n = 50 を要求した場合)、if ステートメントを削除して、10000 個のソリューションすべてを合計値をキーにしたハッシュ テーブルに格納するだけで済みます。 O(1) クエリ用。

ソリューション セットの絞り込み:

ここで必要なのは、基本的にこのソリューションとそのソリューションを比較し、「このソリューションはそのソリューションよりも優れている/劣っている」という比較アルゴリズムです。これにより、ソリューションを並べ替えて最適なソリューションを多数取得するために使用できる並べ替えアルゴリズムを作成したり、単純に最適なソリューションを見つけることができます。

各ソリューションの標準偏差を計算し、標準偏差を比較するだけで、ほとんどのケースを解決できます。これにより、解の長さにどの程度の分散があるかを示す数値が得られます。「標準偏差が低いほど良い」を使用すると、「大/小の組み合わせよりも、長さが等しいか、互いに近いものが優先されます」となります。標準偏差のアルゴリズムは非常に簡単なので、実装はあなたに任せます。実際には、C# に関数が組み込まれている可能性が高いです。ゼロの長さを含めないように注意してください。実際には、問題を回避するためにソリューション セットに追加する前に削除する必要があります。私が与えたコード。

ただし、さまざまなソリューションの標準偏差が同じである場合に対処するという、注意が必要な部分があります。これが起こるのは2つのケースだけだと思います。

  1. 1 つ目は、複数の理想的なソリューションがある場合にのみ発生します。n = 24 の場合、3 つの解は [8,8,8]、[12,12]、および [24] になります。

  2. 2 つ目は、アルゴリズムのブルート フォースの性質が原因で発生し、非常に多くのソリューションがある理由です。これは、[8,10,12,14] (4 つの一意の長さ) のようなすべてのソリューションに対して、[14,12,10,8] や [12,10,14,8] など、それらの長さを配置する方法が 24 通りあるためです。 ]。したがって、ブルート フォース アルゴリズムを改善する最善の方法は、[0、8、10、12、14、16、18、20、22、24] から 4 つの要素を複数選択するアルゴリズムを使用することです。これにより、ソリューション セットが 715 ソリューションのみに絞り込まれます。もちろん、実際に [8,10,12,14]、[14,12,10,8]、および [12,10,14,8] を別のソリューションとして使用したい場合は、できることはあまりありません。

上記の 2 つの段落は、「場合による」という領域に真っ向から当てはまります。それぞれのケースでアルゴリズムが従うべき規則を決定する必要がありますが、同一の標準偏差を見つけることができるのはこれらの 2 つのケースだけだと思います。

于 2013-01-16T11:03:11.650 に答える
2

すべてが偶数なので、すべてを 2 で割ります。現在、長さ 4 ~ 12 のトラック ピースがあり、全長は約 10 ~ 50 です。

達成しなければならない長さにnという名前を付けます。考えられるすべての数kのトラック ピース (通常は 1 から 4 ですが、たとえば、 n < 16 の場合は 1 から 3、 n > 36 の場合は 3 から 4) に対して、長さn/k +1 およびknのn%kピースを取得することを提案します。長さn/kの%k個。

「/」は整数除算を指定し、「%」は剰余を指定します。

于 2013-01-16T14:33:53.540 に答える
1

救助に数学!

8 より大きいすべての偶数は、このセットの要素の線形結合であることを確認できます - Math Overflow で証明を求めてください ;)。

それでは、この問題を数学で言い換えてみましょう。

  • 基底ベクトルの完全すぎる辞書があります (たとえば、16 は 8 の倍数であるため)。
  • これらの基底ベクトルの線形結合として 8 より大きいすべての偶数を表すことができます。
  • この入力ベクトルのゼロ「ノルム」を最小化しようとしています。

朗報: これは非常に興味深い問題であり、多くのアプリケーション ドメインが存在するため、十分に研究されています。

悪いニュース: これはまだ困難な (NP 困難な) 問題です。

しかし、ねえ、少なくとも今、あなたは知っています。

編集: そして、哲学的な答えを手渡すことで非難されないようにSolver.recursiveSolve、目標に一致するセグメントの組み合わせを徹底的に検索する、修正された (完全に最適化されていない) バージョンがあります。結果をソートできるゼロノルム比較クラス:

    private void RecursiveSolve(int goal, int currentSum,
        List<int> included, List<int> segments, int startIndex)
    {

        for (int index = 0; index < segments.Count; index++)
        {
            int nextValue = segments[index];
            if (currentSum + nextValue == goal)
            {
                List<int> newResult = new List<int>(included);
                newResult.Add(nextValue);
                mResults.Add(newResult);
            }
            else if (currentSum + nextValue < goal)
            {
                List<int> nextIncluded = new List<int>(included);
                nextIncluded.Add(nextValue);
                RecursiveSolve(goal, currentSum + nextValue,
                    nextIncluded, segments, startIndex++);
            }
        }
    }

class ZeroNormAndSDComparer : IComparer<List<int>>
{
    private int l0_norm(List<int> v)
    {
        int norm = 0;
        HashSet<int> seen = new HashSet<int>();

        for (int i = 0; i < v.Count; ++i)
        {
            if (!seen.Contains(v[i]))
            {
                norm++;
                seen.Add(v[i]);
            }
        }
        return norm;
    }

    private static double StandardDeviation(List<int> v)
    {
        double M = 0.0;
        double S = 0.0;
        int k = 1;
        foreach (double value in v)
        {
            double tmpM = M;
            M += (value - tmpM) / k;
            S += (value - tmpM) * (value - M);
            k++;
        }
        return Math.Sqrt(S / (k - 1));
    }

    public int Compare(List<int> x, List<int> y)
    {
        int normComparison = l0_norm(x).CompareTo(l0_norm(y));
        if  (normComparison == 0)
        {
            return StandardDeviation(x).CompareTo(StandardDeviation(y));
        }
        return normComparison;
    }
}

そして、変更されたメイン (結果が個別の 4 セグメントの結果に縮小された後に並べ替えが行われるようになりました):

        List<int> nums = new List<int>() { 8, 10, 12, 14, 16, 18, 20, 22, 24 };

        int width = 80;
        Console.WriteLine("Total width: " + width);

        Solver solver = new Solver();
        List<List<int>> res = solver.Solve(width, nums.ToArray());
        Console.WriteLine("total :" + res.Count);
        var res1 = res.Distinct(new SequenceComparer<List<int>, int>()).ToList();
        Console.WriteLine("total res1:" + res1.Count);

        var res2 = res1.Where(l => l.Count == 4).ToList();
        Console.WriteLine("total res2:" + res2.Count); //reduce to 4 integer solutions

        res2.Sort(new ZeroNormAndSDComparer());
于 2013-01-16T10:45:08.943 に答える