【问题标题】:Where is the flaw in my algorithm for consolidating gold mines?我整合金矿的算法的缺陷在哪里?
【发布时间】:2016-12-07 06:42:31
【问题描述】:

设置是,给定一个N 对象列表,例如

class Mine
{
    public int Distance { get; set; } // from river
    public int Gold { get; set; } // in tons
}

将黄金从一个矿山转移到另一个矿山的成本是

    // helper function for cost of a move
    Func<Tuple<Mine,Mine>, int> MoveCost = (tuple) => 
        Math.Abs(tuple.Item1.Distance - tuple.Item2.Distance) * tuple.Item1.Gold;

我想将黄金合并到K 矿场。

我写了一个算法,想了很多遍,但不明白为什么它不起作用。希望我的cmets能帮上忙。知道我哪里出错了吗?

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

class Mine
{
    public int Distance { get; set; } // from river
    public int Gold { get; set; } // in tons
}

class Solution 
{
    static void Main(String[] args) 
    {
        // helper function for reading lines
        Func<string, int[]> LineToIntArray = (line) => Array.ConvertAll(line.Split(' '), Int32.Parse);

        int[] line1 = LineToIntArray(Console.ReadLine());
        int N = line1[0], // # of mines
            K = line1[1]; // # of pickup locations

        // Populate mine info
        List<Mine> mines = new List<Mine>();
        for(int i = 0; i < N; ++i)
        {
            int[] line = LineToIntArray(Console.ReadLine());
            mines.Add(new Mine() { Distance = line[0], Gold = line[1] });
        }

        // helper function for cost of a move
        Func<Tuple<Mine,Mine>, int> MoveCost = (tuple) => 
            Math.Abs(tuple.Item1.Distance - tuple.Item2.Distance) * tuple.Item1.Gold;

        // all move combinations
        var moves = from m1 in mines
                    from m2 in mines
                    where !m1.Equals(m2)
                    select Tuple.Create(m1,m2);

        // moves in ascending order of cost
        var ordered = from m in moves
                      orderby MoveCost(m)
                      select m;

        int sum = 0; // running total of move costs
        var spots = Enumerable.Repeat(1, N).ToArray(); // spots[i] = 1 if hasn't been consildated into other mine, 0 otherwise
        var iter = ordered.GetEnumerator();
        while(iter.MoveNext() && spots.Sum() != K)
        {
            var move = iter.Current; // move with next smallest cost
            int i = mines.IndexOf(move.Item1), // index of source mine in move
                j = mines.IndexOf(move.Item2); // index of destination mine in move
            if((spots[i] & spots[j]) == 1) // if the source and destination mines are both unconsolidated
            {
                sum += MoveCost(move); // add this consolidation to the total cost
                spots[i] = 0; // "remove" mine i from the list of unconsolidated mines 
            }
        }

        Console.WriteLine(sum);
    }
}

我失败的测试用例的一个例子是

3 1
11 3
12 2
13 1

我的输出是

3

正确的输出是

4

【问题讨论】:

  • 您是否尝试过设置断点并查看数据?
  • 似乎您的代码甚至无法编译。 if(spots[i] &amp; spots[j] == 1) 是否符合预期?
  • @LucMorin 忽略这个错误
  • 我不太确定我是否理解 spots 变量的使用,这似乎在您的示例数据中限制为只有 2 个求和操作。你能澄清一下你的想法吗?
  • 我不得不承认不了解实际目标,因此通过您的代码确实会产生“不希望的”值,但我想了解您是如何达到“希望的”值的。你能用简单的古英语解释这个算法吗?有时在算法上加上文字会“迫使”解决方案公开;-)

标签: c# algorithm linq optimization complexity-theory


【解决方案1】:

另一个答案确实指出了实现中的一个缺陷,但它没有提到在您的代码中,您实际上并没有更改其余 Mine 对象中的 Gold 值。因此,即使您确实对数据进行了重新排序,也无济于事。

此外,在每次迭代中,您真正关心的只是 最小值 值。对整个数据列表进行排序是多余的。您只需扫描一次即可找到价值最低的项目。

您也不需要单独的标志数组。只需将您的移动对象保留在列表中,然后在选择移动后,删除包含 Mine 的移动对象,否则您会标记为不再有效。

这是您的算法的一个版本,其中包含了上述反馈:

    static void Main(String[] args)
    {
        string input =
@"3 1
11 3
12 2
13 1";
        StringReader reader = new StringReader(input);

        // helper function for reading lines
        Func<string, int[]> LineToIntArray = (line) => Array.ConvertAll(line.Split(' '), Int32.Parse);

        int[] line1 = LineToIntArray(reader.ReadLine());
        int N = line1[0], // # of mines
            K = line1[1]; // # of pickup locations

        // Populate mine info
        List<Mine> mines = new List<Mine>();
        for (int i = 0; i < N; ++i)
        {
            int[] line = LineToIntArray(reader.ReadLine());
            mines.Add(new Mine() { Distance = line[0], Gold = line[1] });
        }

        // helper function for cost of a move
        Func<Tuple<Mine, Mine>, int> MoveCost = (tuple) =>
            Math.Abs(tuple.Item1.Distance - tuple.Item2.Distance) * tuple.Item1.Gold;

        // all move combinations
        var moves = (from m1 in mines
                    from m2 in mines
                    where !m1.Equals(m2)
                    select Tuple.Create(m1, m2)).ToList();

        int sum = 0, // running total of move costs
            unconsolidatedCount = N;
        while (moves.Count > 0 && unconsolidatedCount != K)
        {
            var move = moves.Aggregate((a, m) => MoveCost(a) < MoveCost(m) ? a : m);

            sum += MoveCost(move); // add this consolidation to the total cost
            move.Item2.Gold += move.Item1.Gold;
            moves.RemoveAll(m => m.Item1 == move.Item1 || m.Item2 == move.Item1);
            unconsolidatedCount--;    
        }

        Console.WriteLine("Moves: " + sum);
    }

如果您的问题没有更多详细信息,我无法保证这实际上符合规范。但它确实为sum 产生了值4。 :)

【讨论】:

  • @user6048670:Stack Overflow 不是让别人为某些第三方网站编写代码的地方。根据您在your 问题中提供的规范,我所能提供的只是修复your 代码。以上解决了您在实现您的对问题的理解中的错误,但不可能解决这种理解。您需要提供更多细节,坦率地说,这样做后您可能会发现问题过于宽泛,无法成为一个好的 Stack Overflow 问题。
  • @user6048670:如果您愿意,可以在您的问题中提供额外的测试用例,但没有更好地描述您实际应该解决的问题以及为什么 “正确”的答案实际上是正确的,不可能提供更好的答案。
  • 这个算法也有一个缺陷,因为你不知道哪个动作会更好,将#2放入#3,或#3放入#2,如果它们的数量相同金子的。假设测试数据是3 1 11 3 12 1 13 1,正确的合并是#3到#2,然后#2到#1,或#3到#1,#2到#1,总成本为3,算法可以产生将 #2 放入 #3,然后将 #3 放入 #1,总费用为 5。
  • @Vesper:您可能是对的,但“此算法”只是问题中给出的代码中预期的算法的正确实现。我没有改变设计,只是改变了实现。如果他们想要一个完整的答案,OP 将需要提供一个完整的规范。到目前为止,给出的唯一“规范”是“它应该返回值4”。甚至没有说明结果应该优化什么,或者如果是的话,应该优化什么(移动次数?移动成本?合并后成本?)。
【解决方案2】:

当你将矿i合并到矿j中时,矿j中的黄金数量会增加。这使得从矿井 j 到其他矿井的合并变得更加昂贵,可能会导致通过移动成本对矿井进行排序无效。要解决此问题,您可以在 while 循环的每次迭代开始时重新排序地雷列表。

【讨论】:

    猜你喜欢
    • 2017-06-20
    • 1970-01-01
    • 2017-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-14
    • 2023-03-18
    • 2020-06-06
    相关资源
    最近更新 更多