【问题标题】:Find logic behind best approach寻找最佳方法背后的逻辑
【发布时间】:2019-11-07 03:04:31
【问题描述】:

我正在尝试解决在线法官问题。问题描述:

这是原来的问题:https://www.urionlinejudge.com.br/judge/en/problems/view/1090

这里是输入的完整列表: http://maratona.ime.usp.br/hist/2008/primeira-fase/testset2008/principal/G.in (警告:巨大的 ~10mb 文件)

输出:http://maratona.ime.usp.br/hist/2008/primeira-fase/testset2008/principal/G.sol

  • Set 是一种纸牌游戏,其中每张纸牌可能有一个、两个或三个数字。
  • 一张给定卡片中的所有数字都是相等的。
  • 图形可以是圆形、正方形或三角形。
  • 每张卡片因此具有两个特征:数字的数量和类型图。

一组是一组三张卡片,对于每个特征(number 和 figure): 三张卡片相等或三张卡片不同。

  1. (a) 是一个有效的集合,因为所有卡片都具有相同的图形类型并且 他们都有不同的数字。
  2. (b), 图形的类型和图形的数量都不同, 制作也是一个有效的集合。另一方面,
  3. (c) 不是有效集合, 因为最后两张牌有相同的数字,这与 第一张卡片中的人物。
  4. (d)和(e)有效,图类型相同,图号相同。

对于非常大的牌组(给定的牌数),找到可以找到最多组数的最佳方法可能会很麻烦。

在某些情况下,通过按此特定顺序删除,所有:

  1. 图形类型相同,数量不同。
  2. 不同的图形类型,不同的数量。
  3. 相同的图形类型,相同的数量。

是最佳方法,但在某些情况下不是,在其他情况下,按以下顺序删除:

  1. 不同的图形类型,不同的数量。
  2. 图形类型相同,数量不同。
  3. 相同的图形类型,相同的数量。

是最佳方法,最后,在其他情况下删除:

相同的图形类型,相同的数量......等等是最好的最优方法。

我可以暴力破解它并执行所有搜索并使用找到最多的搜索,但这显然并不理想。现在,我一直试图找出在特定情况下使一种方法比另一种方法更好的潜在问题,但我就是想不通。我试过检查even/odd每种卡的数量,但没有正确和错误方法之间的模式。

关于如何找到最佳方法的任何想法?代码:

#include <iostream>
enum CardType
{
    SQUARE_ONE,
    SQUARE_TWO,
    SQUARE_THREE,
    CIRCLE_ONE,
    CIRCLE_TWO,
    CIRCLE_THREE,
    TRIANGLE_ONE,
    TRIANGLE_TWO,
    TRIANGLE_THREE
};
constexpr int FIGURE_MAX = 9;

class Set
{
private:
    int m_Amount {0};
public:
    int GetAmount() const { return m_Amount; }
private:
    void IncreaseAmountOfMatches() {m_Amount++;}
    int m_arrCards[FIGURE_MAX] {0};
public:
    void ReadCards(int N);
    void PerformMatching();

    void PerformMatching_SameType_DiffAmount();
    void PerformMatching_DiffType_DiffAmount();
    void PerformMatching_SameType_SameAmount();

    void Reset();
private:

    CardType GetCardByName(const std::string& name);
    inline void DecreaseType(CardType type1, CardType type2, CardType type3);
    inline void DecreaseThreeOfExactlyEqualType(CardType type);
};

void Set::Reset()
{
    m_Amount = 0;
    for(int i{0}; i<FIGURE_MAX; i++) {
        m_arrCards[i] = 0;
    }
}

void Set::ReadCards(int N)
{
    Reset();
    static std::string name;
    for(int i{0}; i<N; i++){
        std::getline(std::cin, name);
        m_arrCards[GetCardByName(name)]++;
    }
}
inline void Set::DecreaseThreeOfExactlyEqualType(CardType type)
{
    if(m_arrCards[type] >= 3) {
        m_arrCards[type] -= 3;
        IncreaseAmountOfMatches();
    }
}
inline void Set::DecreaseType(CardType type1, CardType type2, CardType type3)
{
    if(m_arrCards[type1] && m_arrCards[type2] && m_arrCards[type3])
    {
        m_arrCards[type1]--;
        m_arrCards[type2]--;
        m_arrCards[type3]--;
        IncreaseAmountOfMatches();
    }
}

void Set::PerformMatching_SameType_DiffAmount()
{
    while(true) //SAMETYPE, DIFFAMOUNT
    {
        int currentAmount = GetAmount();
        //Same Square
        DecreaseType(SQUARE_ONE, SQUARE_TWO, SQUARE_THREE);
        //Same Circle
        DecreaseType(CIRCLE_ONE, CIRCLE_TWO, CIRCLE_THREE);
        //Same Triangle
        DecreaseType(TRIANGLE_ONE, TRIANGLE_TWO, TRIANGLE_THREE);

        if(currentAmount == GetAmount()) {
            break;
        }
    }
}

void Set::PerformMatching_DiffType_DiffAmount()
{
    while(true) //DIFFTYPE, DIFFAMOUNT
    {
        const int currentAmount = GetAmount();

        //Different Squares
        DecreaseType(SQUARE_ONE, CIRCLE_TWO, TRIANGLE_THREE);
        DecreaseType(SQUARE_ONE, TRIANGLE_THREE, CIRCLE_TWO);

        DecreaseType(SQUARE_TWO, CIRCLE_ONE, TRIANGLE_THREE);
        DecreaseType(SQUARE_TWO, CIRCLE_THREE, TRIANGLE_ONE);

        DecreaseType(SQUARE_THREE, CIRCLE_ONE, TRIANGLE_TWO);
        DecreaseType(SQUARE_THREE, CIRCLE_TWO, TRIANGLE_ONE);

        //Different Circles
        DecreaseType(CIRCLE_ONE, SQUARE_TWO, TRIANGLE_THREE);
        DecreaseType(CIRCLE_ONE, SQUARE_THREE, TRIANGLE_TWO);

        DecreaseType(CIRCLE_TWO, SQUARE_ONE, TRIANGLE_THREE);
        DecreaseType(CIRCLE_TWO, SQUARE_THREE, TRIANGLE_ONE);

        DecreaseType(CIRCLE_THREE, SQUARE_TWO, TRIANGLE_ONE);
        DecreaseType(CIRCLE_THREE, SQUARE_ONE, TRIANGLE_TWO);

        //Different Triangles
        DecreaseType(TRIANGLE_ONE, SQUARE_TWO, CIRCLE_THREE);
        DecreaseType(TRIANGLE_ONE, SQUARE_THREE, CIRCLE_TWO);

        DecreaseType(TRIANGLE_TWO, SQUARE_ONE, CIRCLE_THREE);
        DecreaseType(TRIANGLE_TWO, SQUARE_THREE, CIRCLE_ONE);

        DecreaseType(TRIANGLE_THREE, SQUARE_ONE, CIRCLE_TWO);
        DecreaseType(TRIANGLE_THREE, SQUARE_TWO, CIRCLE_ONE);

        //No more matches available
        if(currentAmount == GetAmount()) {
            break;
        }
    }
}

void Set::PerformMatching_SameType_SameAmount()
{
    while(true)//SAMETYPE, SAMAMOUNT
    {
        int currentAmount = GetAmount();

        DecreaseThreeOfExactlyEqualType(SQUARE_ONE);
        DecreaseThreeOfExactlyEqualType(SQUARE_TWO);
        DecreaseThreeOfExactlyEqualType(SQUARE_THREE);

        DecreaseThreeOfExactlyEqualType(CIRCLE_ONE);
        DecreaseThreeOfExactlyEqualType(CIRCLE_TWO);
        DecreaseThreeOfExactlyEqualType(CIRCLE_THREE);

        DecreaseThreeOfExactlyEqualType(TRIANGLE_ONE);
        DecreaseThreeOfExactlyEqualType(TRIANGLE_TWO);
        DecreaseThreeOfExactlyEqualType(TRIANGLE_THREE);

        if(currentAmount == GetAmount()) {
            break;
        }
    }
}

void Set::PerformMatching()
{
    //how to know the best approach?
    bool bestApproach = true; //what to do here?
    if(bestApproach) //.....
    {
        PerformMatching_SameType_DiffAmount();
        PerformMatching_DiffType_DiffAmount();
    }
    else
    {
        PerformMatching_DiffType_DiffAmount();
        PerformMatching_SameType_DiffAmount();
    }
    PerformMatching_SameType_SameAmount();
}

CardType Set::GetCardByName(const std::string& name)
{
    if(!name.compare("um quadrado")) return CardType::SQUARE_ONE;
    if(!name.compare("dois quadrados")) return CardType::SQUARE_TWO;
    if(!name.compare("tres quadrados")) return CardType::SQUARE_THREE;

    if(!name.compare("um circulo")) return CardType::CIRCLE_ONE;
    if(!name.compare("dois circulos")) return CardType::CIRCLE_TWO;
    if(!name.compare("tres circulos")) return CardType::CIRCLE_THREE;

    if(!name.compare("um triangulo")) return CardType::TRIANGLE_ONE;
    if(!name.compare("dois triangulos")) return CardType::TRIANGLE_TWO;

    return CardType::TRIANGLE_THREE;
}

int main()
{
    static int cardAmount {0};
    Set gameInstance;
    while(true)
    {
        std::cin >> cardAmount;
        std::cin.ignore();
        if(cardAmount == 0) {
            break;
        }

        gameInstance.ReadCards(cardAmount);
        gameInstance.PerformMatching();
        std::cout << gameInstance.GetAmount() << "\n";

    }
    return 0;
}

【问题讨论】:

  • 一副牌的数量限制是多少?预期的时间复杂度是多少?你能分享原始问题的链接吗?
  • 我刚刚注意到,在您的列表中,您忘记提及不同的图形类型,相同的数量。据我了解,这也应该是一个有效的组合?
  • @ViníciusMagalhãesHorta 是的,会的。如果数字都不同,但数量总是相同的,那就是一组。从问题:“一组是一组三张卡片,对于每个特征(数字和图形),三张卡片相等或三张卡片不同。”
  • @ViníciusMagalhãesHorta 是的,它是一组。对于每个数字,它们都是不同的。对于每个数字,它们都是相同的。根据我上面粘贴的问题的定义,这使它成为一个集合。这种缺乏对问题规则的理解很容易导致缺乏统一的解决方案。我对 Set 游戏并不陌生。
  • @sweenish,我终于解决了这个问题。你是对的different figure type, same amount 确实是一组卡片.... w-o-w!我再也不会这么快地忽略别人告诉我的事情了。对不起。

标签: c++


【解决方案1】:

这个问题的关键是要明白,贪婪的策略是行不通的,因为总是可以构建一个反例来打败它。

您需要处理所有组合才能战胜这个问题,但有一个重要警告:您可以通过删除同一张牌的每三组来简化问题,然后解决剩余牌的问题。我很惊讶我们只需要保留每种类型的 1-3 张卡片即可获得正确的结果。

所以用伪代码解决:

allowed_sets = ... // Determine the set of all valid sets of 3 cards

simple_problem, number_of_sets_simplified = simplify(problem) // Reduce problem by removing triples of cards

result = number_of_sets_simplified + solve(simple_problem)

int solve(Problem problem)
{     
   int best_score = 0;
   for (set : allowed_sets)
   {
     if (problem.can_remove(set))
     {
       int score = solve(problem.remove(set)) + 1;
       if (best_score < score)
         best_score = score;
     }
   }
   return best_score;
}

Ruby 中的解决方案(为已计算的解决方案提供缓存,并使用队列实现深度优先搜索,因为我担心堆栈溢出):

# Build array of allowed sets
cards = ["um", "dois", "tres"].product(["quadrado", "circulo", "triangulo"])
allowed_sets = cards.repeated_combination(3).to_a
allowed_sets.select! { |s|
  [0,1].all? { |i|
  (s[0][i] == s[1][i] && s[0][i] == s[2][i]) ||
  (s[0][i] != s[1][i] && s[0][i] != s[2][i] && s[1][i] != s[2][i])
  }
}

puts allowed_sets.inspect

# Convert to index array for the number of cards in the set for each of the 9 possible cards
allowed_sets.map! { |s|
  nset = [0] * 9

  s.each { |l|
    nset[cards.find_index(l)] += 1
  }

  nset
}

puts allowed_sets.inspect


# Read problems from G.in
problems = []
nset = nil
File.readlines("G.in", chomp: true).each { |l|

  if l =~ /\d+/
    problems << nset if nset
    nset = [0] * 9
  else 
    i = cards.find_index(l.chomp("s").split)
    nset[i] += 1
  end  
}

cache = {}
queue = []

problems.each { |problem|

  # Simplify task by removing as many triple sets of the same card without being greedy
  result = 0
  problem.map! { |i| 

    if i < 1
      i
    else
      a = (i-1) / 3
      result += a
      i - 3 * a
    end
  }

  puts "Problem to solve: " + problem.inspect

  queue = [problem]  
  while queue.size > 0
    current_set = queue.pop

    any_missing = false
    max = nil

    any_allowed = false

    # Check each set
    allowed_sets.each { |set_to_check|

      # Do we have enough remaining cards for this set
      if (0..8).to_a.all? { |i| set_to_check[i] <= current_set[i] } 

        next_set = current_set.dup
        (0..8).to_a.each { |j| next_set[j] -= set_to_check[j] }

        if next_set.sum < 3
          cache[next_set] = 0
        end

        if cache.has_key?(next_set)
          max = cache[next_set] + 1 if !max || max < cache[next_set] + 1
        else
          queue << current_set if !any_missing
          queue << next_set
          any_missing = true
        end

        any_allowed = true
      end

    }

    if any_allowed
      if !any_missing
        cache[current_set] = max
      end 
    else
      cache[current_set] = 0      
    end

  end

  puts "Number of sets: #{cache[problem] + result}"
}

【讨论】:

  • 我不完全理解你的意思“你可以通过删除同一张卡片的每三组来简化问题,然后解决剩下的卡片的问题。我很惊讶我们只需要保留每种类型的 1-3 张卡 [...]" 您的意思是保留 3 张相同类型的卡用于最终解决方案就足够了,还是您的意思是所有相同类型的卡都可以如果可能,删除?
  • 如果你有9张相同类型的卡片,你可以用3解决问题,并在你的最终结果中添加(9-6) / 3 = 2。
  • 我不知道如何实现 C++ 版本,因为我无法理解 Ruby 代码逻辑
  • @ViníciusMagalhãesHorta 今天晚些时候我将发布一个很长的答案;我认为 Christopher 的结果虽然鼓舞人心,但并不完全正确,尤其是可以找到更优雅的解决方案。
  • @ViníciusMagalhãesHorta 对不起,我今天不做;我发现我的解决方案有问题。我希望我仍然可以解决它并尽快回复您(主要是因为我喜欢这个问题)。
【解决方案2】:

给其他读者的一般说明:问题的作者明确评论说只有三种集合类型相同的图形类型,相同的数量,相同的图形类型,不同的数量和不同的图形类型,不同的金额有效。

此问题不应与链接集问题混淆,其中不同图形类型,相同数量的情况也是有效的。


以下示例表明,您提出的所有启动操作都会导致无法再达到最佳状态。

为了简短的统一标识符,我使用c 表示circulo(s),q 表示quadrado(s) 和t 表示triangulo(s),后跟数字1、2 或@987654329 @ 而不是 um、dois、tres。

示例输入:

c1
c1
c1
c2
c2
c3
q1
q1
q2
q2
q3
q3
t1
t1
t1
t2
t3
t3

开始1:相同的图形类型,相同的数量。

c1 c1 c1
t1 t1 t1

既然您评论说这可以通过接下来应用相同类型,不同数量来解决:

q1 q2 q3
q1 q2 q3

种类不同,数量不同

No match.
Remaining:
c2
c2
c3
t2
t3
t3

开始2:相同的图形类型,不同的数量。

c1 c2 c3
q1 q2 q3
q1 q2 q3
t1 t2 t3

开始3:不同的图形类型,不同的数量。

c1 q2 t3
c1 q2 t3
c1 t2 q3
t1 c2 q3

观察

在每次开始时,尝试使用其他两种策略解决剩余项目不会将所有项目解决为集合(即使您尝试聪明而不是一个接一个地应用它们)。

实际的解决方案会解决成完美数量的集合

c1 c1 c1
q1 q2 q3
t1 t2 t3
q1 c2 t3
t1 c2 q3
t1 q2 c3

如您所见,在 3 次开始中的每一次中,都使用了一种选择策略,直到不再适用为止,选择了一组,这不是完美解决方案的一部分。相反,这个额外的集合会导致一种情况,即缺少关键项目来完成具有不同策略的集合。

因此我们可以得出结论,最好的方法通常不能只包括以任何顺序应用三种不同的策略,即使有输入示例,按顺序应用这些策略是有效的。

【讨论】:

  • 呃,您的解决方案似乎适用:1:same type, same amount -> 2:same type, different amount -> 3:different type, different amount...
  • 在使用我的程序时,它对某些牌组(如您的)是最佳的,而另一些则不是。一些甲板; 1:different type, different amount -> 2:same type, different amount -> 3: same type, same amount 将是最佳的。其他一些 1:same type, different amount -> 2:same type, same amount -> 3:different type, different amount 是最佳的......等等等等。我猜你明白了
  • 排除有效匹配策略要么突出了 OP 对集合是什么的误解,要么他们试图解决的问题存在根本缺陷。请注意,在该页面上,相同的形状但不同的数字是有效的集合。不计算倒数对某人来说是一个巨大的错误。
  • @ViníciusMagalhãesHorta 我将您建议的继续编辑编辑为 Start 1。这是如何解决的?还是我错过了什么?
  • @ViníciusMagalhãesHorta 除了所有逻辑问题外,我刚刚意识到您的 PerformMatching_DiffType_DiffAmount 代码看起来错误。 DecreaseType(SQUARE_ONE, CIRCLE_TWO, TRIANGLE_THREE); 和 DecreaseType(SQUARE_ONE, TRIANGLE_THREE, CIRCLE_TWO); 基本上是在移除同一组卡片。也许你还有更多这样的错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-06
  • 1970-01-01
  • 2022-01-02
  • 2012-06-04
  • 2017-05-07
  • 1970-01-01
相关资源
最近更新 更多