【问题标题】:Collecting points game收集积分游戏
【发布时间】:2013-03-24 12:29:03
【问题描述】:

有一个 2 人游戏,你有一个数字序列,例如:2 -6 12。假设它们是写在卡片上的。

游戏需要时间轮流。每个回合玩家有义务从序列的开头或结尾拿取一张牌(不可跳过)。取完最后一张牌后游戏结束。目的是以尽可能高的正分数完成游戏(分数是玩家所拿卡上所有数字的总和)。我们也知道两个玩家都使用最优策略(最大化他们的收益)。我们必须说他们最终会达到什么分数。

知道最优策略是什么样的吗?

到目前为止我的研究:

1-3 cards is trivial
{a}, no choice take a;
{a,b} take max(a,b) reduces to problem {a}
{a,b,c} take max(a,c) reduces to problem {a,b}
4 cards : {a,b,c,d}
if (a + max(c, min(b,d)) > d + max(b, min(a,c)))
    take a;
else
    take d;

如果我决定取a,我的对手取max(b,d),正如3张牌策略所说,所以我要做的是从c取最大值(这在对手回合中是“安全的”) , 和小于b, d 的牌,因为对手会拿更大的牌。与d 的双重情况。但我不知道如何扩展(如果可能的话)n-cards case。

有什么线索吗?

【问题讨论】:

    标签: algorithm optimization


    【解决方案1】:
    //globals
    int[N] cards;
    int[N][N] v;  //initialized to 0
    int[N][N] sum; // precomputed such that sum(i,j)=cards[i]+...+cards[j]
    
    void updateValue(int i,int j){
        int left=cards[i]+sum(i+1,j)-v(i+1,j);
        int right=cards[j]+sum(i,j-1)-v(i,j-1);
        v[i,j]=max(left,right);
    }
    
    void do(){
        for (int d=1;d<N;d++)
            for (int i=0;i<N-d;i++)
                updateValue(i,i+d);  
    }
    

    答案将在值[0,N-1]中

    可以通过注意以下几点来提高内存使用率:如果我们将 v 视为一个大方桌,我们会填充它的上三角形一半。对于外部循环中的每个 d 值,我们填充一个从 [0,d] 到 [N-d,N-1] 的 diagonal。现在请注意,在填充这条对角线时,我们只使用之前最后一条对角线的值,因此我们实际上不需要将所有先前的值保存在内存中。

    【讨论】:

    • 很抱歉长时间不活动,但我已经放弃了获得答案的希望。无论如何,让我们进入正题:这个看起来比以前的好得多。首先,我有一个抱怨记忆 (10^5)^2 = 10^10 有点太高了。但我认为我们可以解决这个问题。 sum 可以只是 int[N] 在每个 sum[index] = sum[index-1] + cards[index] 上,现在我可以使用一个减法轻松获得区间 (a,b) 1 到 b - 1 到 a 给出( a,b) 但不知道如何减少 v (值?)你能描述一下(一些 cmets 并坚持你以前使用的名称,因为它有点令人困惑)算法。
    • 如果你告诉我你在 updateValue 中用 left right 做什么,我将不胜感激,因为它们对我没有意义,但事实上我有预感它可能会起作用。
    • 我认为就是这样。非常感谢您所做的工作。
    【解决方案2】:

    乍一看让我想起了 Knackpack 问题,但后来我意识到这是一个递归问题。你熟悉 OCaml 吗?考虑这个问题的方法是根据“一组功能”。

    需要从基本案例入手,定义基本功能:

    e.g. f1(a) -> a
         f2(x, y ) -> max(x,y)
         f3(x, y, z) -> max(f(x,y),z)
    

    然后您需要定义更复杂的情况,如下所示:

    if (a + max(c, min(b,d)) > d + max(b, min(a,c)))
        take a;
    else
        take d;
    

    这看起来像一个 4 输入函数,其中您使用先前定义的 max 和 min。 像这样的:

    f2 (a, b,c, d) -> if (a + max(c, min(b,d)) > d + max(b, min(a,c))) then true, else false
    f3(a, b,c, d) -> if(f2(a,b,c,d) then a else d
    

    如果需要,您需要定义“基本”函数 f2、f3 和其他函数,然后将输入值替换为其他函数的输出。

    我知道这不是一个解决方案,但希望是一个足够好的提示,可以开始以递归方式进行推理。

    【讨论】:

      【解决方案3】:

      我认为这样的事情会起作用:

      int[] cards;
      
      int low = 0;
      int high = cards.length - 1;
      
      int bestScore(int low, int high)
      {
        if (low > high)
          return 0;
      
        // our best score is our immediate score minus the opponents best response
        int lowScore = cards[low] - bestScore(low + 1, high);
        int highScore = cards[high] - bestScore(low, high - 1);
      
        if (lowScore >= highScore)
          return lowScore;
        else
          return highScore;
      }
      
      int bestMove(int low, int high)
      {
        int lowScore = cards[low] - bestScore(low + 1, high);
        int highScore = cards[high] - bestScore(low, high - 1);
      
        if (lowScore >= highScore)
          return low;
        else
          return high;
      }
      

      【讨论】:

      • 老实说,它来自巴西 ICPC。对我来说,这似乎是动态编程问题。设置最多 10,000 张卡片。如果我理解您的递归算法将花费比宇宙生命更多的时间。它与斐波那契递归计算(两个递归调用)非常相似。我没有说(无意中省略)的是卡号是偶数(如果它有帮助的话)
      • 是的,对于 10,000 张卡片,这种方法需要永远平方:) 但是,您可以将搜索深度限制在合理的范围内,例如20 或基于已知的运行时间有一些其他限制,并且可能得到比至少人类对手更好的响应。
      • uva.onlinejudge.org/… 对于几个测试用例,C 卡肯定必须在 3 秒内成为问题。好吧,我希望有人能找到这个话题来帮助我。
      【解决方案4】:

      如果序列的长度是偶数则回答:

      第一个玩家总是有一个不会松懈的策略。关键的观察是,第一个玩家可以收集所有偶数位的数字,如果他愿意,他可以收集所有奇数位的数字。所以他只需要事先检查哪个总和更大:

      如果序列是 {x_1, x_2, ...,x_n} 其中 n=2k

      计算:A=x_1+x_3_...x_2k-1 和 B=x_2+x_4+...+x_2k

      如果 A>=B,首先选择 x_2k,无论对手做什么,第一个玩家总是可以选择 x_2i 获得一些 i。如果 A

      【讨论】:

      • 你错了。让序列 {a,b,c,d} 如下 {20, -5, 20, 44} 然后 a+c = 20 + 20 = 40 > 39 = 44 - 5 = b+d。假设你从拿20开始。然后对手拿了44你拿了20,对手拿了-5。你的收益是 40,op 是 39。使用人脑我可以执行更好的策略:我取 44,op 取 20 我取 20,op 取 -5。结果:我有 64 个,而对手只有 15 个。
      • 此外,这也不是我的问题的答案,因为无论对手做什么都是非常重要的,他会尝试做动作来增加他的分数(即使他这样做是贪婪的——假设这将是最好的策略,而整个游戏都在第一位玩家身上)。不过,有时取较小的总和会更好,因为后来我仍然有机会从敌人对中取出较小的数字,因为我的第一个帖子案例 #4 状态。
      • @kostek 我的假设是“赢家通吃”,也就是说,只要你的分数大于对手的分数,你就赢了。
      • 这不是一个答案(即它不是 n 多张牌的求解器),但如果有偶数张牌,玩家 1 确实总是可以赢。如果有 odd 张牌,则玩家 1 将获得一张额外的牌(否则最后一张牌将被忽略?),结果将是策略方面的无趣:玩家 2 的策略是不相关/无能为力,或者(在奇数张牌的情况下),结果是固定的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-28
      • 1970-01-01
      • 1970-01-01
      • 2021-08-15
      • 1970-01-01
      相关资源
      最近更新 更多