典型問題    次の典型問題へ    前の典型問題へ

典型問題(初級) C 巡回セールスマン問題


問題へのリンク


C#のソース

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static string InputPattern = "InputX";

    static List<string> GetInputList()
    {
        var WillReturn = new List<string>();

        if (InputPattern == "Input1") {
            WillReturn.Add("4");
            WillReturn.Add("0 3 1 4");
            WillReturn.Add("1 0 5 9");
            WillReturn.Add("2 6 0 5");
            WillReturn.Add("3 5 8 0");
            //12
        }
        else {
            string wkStr;
            while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
        }
        return WillReturn;
    }

    static int mN;
    static int UB;
    static int[,] mMatrixArr;

    static void Main()
    {
        List<string> InputList = GetInputList();
        mN = int.Parse(InputList[0]);
        UB = mN - 1;
        mMatrixArr = new int[UB + 1, UB + 1];

        int[] wkArr = { };
        Action<string> SplitAct = pStr =>
            wkArr = pStr.Split(' ').Select(X => int.Parse(X)).ToArray();

        for (int Y = 0; Y <= UB; Y++) {
            SplitAct(InputList[Y + 1]);

            for (int X = 0; X <= UB; X++) {
                mMatrixArr[X, Y] = wkArr[X];
            }
        }
        Solve();
    }

    struct JyoutaiDef
    {
        internal int CurrNode;
        internal HashSet<int> VisitedSet;
        internal long CostSum;
    }

    static void Solve()
    {
        // 開始ノードは0とする
        var Stk = new Stack<JyoutaiDef>();
        JyoutaiDef WillPush;
        WillPush.CurrNode = 0;
        WillPush.VisitedSet = new HashSet<int>();
        WillPush.CostSum = 0;
        Stk.Push(WillPush);

        // 最小コスト[訪問済ノードと現在ノードのハッシュ値]
        var MinCostDict = new Dictionary<int, long>();

        long Answer = long.MaxValue;
        while (Stk.Count > 0) {
            JyoutaiDef Popped = Stk.Pop();

            // クリア判定
            if (Popped.VisitedSet.Count == mN) {
                if (Popped.CurrNode == 0) {
                    Answer = Math.Min(Answer, Popped.CostSum);
                }
                continue;
            }

            // 解でないのに、ノード0を訪問してたら枝切り
            if (Popped.VisitedSet.Contains(0)) {
                continue;
            }

            for (int X = 0; X <= UB; X++) {
                //再訪問防止
                if (Popped.VisitedSet.Contains(X)) {
                    continue;
                }

                WillPush.CurrNode = X;
                WillPush.CostSum = Popped.CostSum + mMatrixArr[X, Popped.CurrNode];

                // 枝切り
                if (WillPush.CostSum >= Answer) {
                    continue;
                }

                WillPush.VisitedSet = new HashSet<int>(Popped.VisitedSet);
                WillPush.VisitedSet.Add(X);
                int Hash = GetHash(WillPush.VisitedSet, WillPush.CurrNode);
                if (MinCostDict.ContainsKey(Hash)) {
                    if (MinCostDict[Hash] <= WillPush.CostSum) {
                        continue;
                    }
                }
                MinCostDict[Hash] = WillPush.CostSum;
                Stk.Push(WillPush);
            }
        }
        Console.WriteLine(Answer);
    }

    // 訪問済ノードと現在ノードから、ハッシュ値を求める
    // 24ビット使用し、
    // 先頭16ビットが各ノードの訪問有無
    // 末尾8ビットがpCurrNode
    static int GetHash(HashSet<int> pVisitedSet, int pCurrNode)
    {
        int WillReturn = 0;
        int Base = 1;
        for (int I = 0; I <= mN - 1; I++) {
            if (pVisitedSet.Contains(I)) {
                WillReturn += Base;
            }
            Base *= 2;
        }

        // 8ビット左シフト
        WillReturn <<= 8;
        WillReturn += pCurrNode;

        return WillReturn;
    }
}


解説

訪問済ノードと現在ノードから
ハッシュ値を求めて、その時点でのコストでの枝切りや

解候補のコストを超えてないかの枝切りを行ってます。


類題

DPL_2_A: Traveling Salesman Problem