【问题标题】:Minimum number of fire balls required to kill every archer?杀死每个弓箭手所需的最少火球数?
【发布时间】:2017-01-19 14:41:52
【问题描述】:

Polycrap想要杀死一排弓箭手,他唯一的武器是火球。如果 Polycarp 用他的火球击中第 i 个弓箭手(从左到右编号),弓箭手会失去 a 个生命值。

同时该法术会伤害与第 i 个(如果有的话)相邻的弓箭手——他们失去 b (1 ≤ b ) 生命值各分。

由于极限弓箭手(即编号为 1 和 n 的弓箭手)距离很远,火球无法到达他们。 Polycarp 可以用他的火球击中任何其他弓箭手。

每个弓箭手的生命值是已知的。当此数量小于 0 时,将杀死一名弓箭手。Polycarp 可以使用多少法术来杀死所有敌人?

如果射手已经被杀死,Polycarp 可以将他的火球扔到射手身上。

I/O 说明

INPUT -The first line of the input contains three integers n, a, b (3 ≤ n ≤ 10; 1 ≤ b < a ≤ 10). 
       The second line contains a sequence of n integers — h1, h2, ..., hn (1 ≤ hi ≤ 15), where hi is the amount of health points the i-th archer has.

OUTPUT- In the first line print t — the required minimum amount of fire balls.
        In the second line print t numbers — indexes of the archers that Polycarp should hit to kill all the archers in t shots. 

现在我已经编写了一个递归函数,该函数采用一组弓箭手当前的健康状况,并生成每次可能的攻击,直到所有弓箭手都死亡并返回最小值。

但是这种方式太慢了,如何高效解决呢?

注意:我不一定对优化自己的解决方案感兴趣,但对任何其他可能更快的解决方案持开放态度。

Problem Link with some test cases

My current solution

【问题讨论】:

  • 你应该在这里发布你的代码而不是链接(因为只要这个问题链接可能不存在的风险)
  • 提示:想想如何在每一步都杀死第一个活着的弓箭手。如果你遵循这个策略,会有多少受伤的弓箭手
  • 我不明白这句话:“如果射手已经被杀死,Polycarp 可以将他的火球扔给射手。”所以即使弓箭手的生命值
  • @shole 这个条件是必要的,因为他不能在极端情况下攻击某人,所以如果四肢旁边的玩家已经死亡,他仍然可以攻击他以破坏极端情况。

标签: algorithm recursion optimization dynamic-programming


【解决方案1】:

关键是要杀死最左边的弓箭手,你需要通过火球攻击他或他旁边的弓箭手。

  1. 杀死第一个和第n个弓箭手。
  2. 从左边开始。你可以通过向他或他的右边同伴投掷火球来杀死最左边的弓箭手。排列数组 DP,其中将包含诸如剩下的弓箭手数量和最左边三个的生命点(AD 中其他人的生命点)之类的条目。因此,尝试两种可能性之一,检查您是否已经处于这种情况,如果是,则使用 DP 的答案,如果不是递归向下,则展开递归,在递归的每个级别上将数据添加到数组 DP 中。

【讨论】:

  • @Deniss 你是对的,谢谢。我已经删除了第二步,我认为这一步会减小记忆表的大小。
【解决方案2】:

一些想法:

  1. 由于递归调用的开销,递归往往较慢。
  2. 我认为您的递归可能会多次计算相同情况(健康分布)的成本,因为主要攻击不同。因此,保留已计算出的健康分布图及其最低成本应该可以解决此问题。
  3. 在所有人都死了之前,您并不总是需要计算。只要您的成本高于(或等于)您已经找到的最佳解决方案,您就可以跳过其余的计算。

【讨论】:

  • 1.我对任何其他非递归方法持开放态度。 2. 我已经保存了已找到解决方案的已访问状态的哈希图。 3 这可以加快速度,但我不确定能达到多少。同样,我不一定希望我的解决方案得到优化'我对其他方法持开放态度
【解决方案3】:

第一个和最后一个弓箭手只能通过一种可能的方式被杀死,即向相邻的弓箭手射击。击杀两人后调整阵容。如果还有弓箭手还活着,请转到下一步。

只有一个数组。无需复制阵列,因为我们将从拍摄中恢复。恢复如何工作?只需添加您之前减去的内容即可。

最左边的活弓箭手 (LLA) 可以直接射他,也可以射下一个(射前一个没有意义,因为他已经死了)。所以你的递归函数应该射击 LLA,恢复数组,然后射击他附近的弓箭手。

为了减少搜索,您实际上并没有“射击”弓箭手(开始、射击、呼叫、恢复、射击、呼叫、恢复、结束),而是调用函数告诉它射击弓箭手作为第一步函数(开始、拍摄、call_to_shoot、call_to_shoot、恢复、结束)。为什么?因为如果射击呼叫是在 LLA 附近射击,那么您不再“呼叫射击”LLA。这条路已经被覆盖了。

额外的优化是,如果它需要与a 射击相同数量的b 射击来杀死 LLA(用数学说话 ceil(x/b) &lt; floor(x/a + 1))然后跳过直接射击他的调用(a 射击),并立即去拍摄相邻的(b拍摄)。

你应该记下杀死它们所需的最少数量的芽的价值。如果在搜索过程中超过了这个值,不要再往前走。

【讨论】:

    【解决方案4】:

    真是个大问题,我也从这个问题中学到了很多东西。以下解决方案都是基于 0 的。

    (我的解决方案受到@Yola 的启发,如果有帮助,请给他UV)


    我相信这个问题有几个有效的 DP 重复,这是我使用的一个:

    DP(i,a,b,c) := 杀死所有 [0,i] 弓箭手所需的最小火球总数,而我们将 a 球扔到 i-1 th em> 射手,b 球给第 i 个 射手,c 球给第 i+1 个 射手

    我定义 INF 如果这样的状态是无效的(即这样的安排不能杀死所有的 [0,i] 弓箭手)。最初所有状态都是INF

    请注意,在给定的要求中,i16(这导致我提交了 2 个错误答案,因为小于 0 而不是小于或等于 0)。解决方案也总是存在的。

    在我们定义了状态之后,现在我们可以尝试组成一个 DP 解决方案的其他三个组成部分:基本案例、递归公式、最终答案查询


    基本情况

    请注意,我们不能在末端扔球,因此杀死它们的唯一方法是向它们的下一个扔足够的火球。

    要杀死 0-th 弓箭手,我们可以将 [0,17] 火球扔给 1st 弓箭手,也可以将所有 [0,17]火球给2nd弓箭手。当然,如果他们可以杀死 0-th1st 弓箭手,则取它们中的最小值。

    //base case
    FOR(i,0,17) FOR(j,0,17)
        if(i*a+j*b>h[1] && i*b > h[0])
            dp[1][0][i][j] = min(dp[1][0][i][j], i);
    

    递归公式

    当我们得到所有可能的方式(即状态)投掷火球杀死所有 [0,i-1] 弓箭手时,我们可以使用这些信息来计算杀死 [0,i] 弓箭手的状态。 (如果你想象它,它就像一个3单位宽度的滑动窗口,从左到右一直)

    公式是 DP(i, a, b, c) = min(DP(i-1, x, a, b) + b) 对于所有 x AND (a,b,c) 可以杀死 i-th 弓箭手

    回答查询

    请注意,我们不能像第一个弓箭手那样将球扔给最后一个弓箭手,所以在这里我们必须像设置基本情况一样进行检查。

    答案是所有 DP(n-2, a, b, 0) 对于 a,b AND (a,b,0 ) 可以杀死 n-2 th 和 n-1 th (last) 弓箭手。

    请记住,您可以只循环所有以取最小值,因为解决方案必须存在


    状态的实际路径(打印出你必须投掷火球的弓箭手)是一个实施问题。这里我简单地使用了一个简单的递归,即O(n)。因此,整个解决方案是 O(n^5) 其中 n~20

    这是我接受的代码

    #include<bits/stdc++.h>
    #define FOR(x,y,z) for(int (x)=(y); (x)<(z); (x)++)
    #define FER(x,y,z) for(int (x)=(y); (x)<=(z); (x)++)
    #define INF 1<<28
    using namespace std;
    
    int n,a,b,dp[11][17][17][17], h[11];
    
    //Trace the actual solution
    void printAns(int idx, int aa, int bb, int cc){
        if(idx<0) return;
        FOR(i,0,bb) printf("%d ", idx+1);
        FOR(i,0,17){
            if(dp[idx-1][i][aa][bb]+bb == dp[idx][aa][bb][cc]){
                printAns(idx-1, i,aa,bb);
                break;
            }
        }
    }
    
    int main() {
        FOR(i,0,11) FOR(j,0,17) FOR(k,0,17) FOR(p,0,17) dp[i][j][k][p] = INF;
        
        scanf("%d%d%d", &n,&a,&b);
        FOR(i,0,n) scanf("%d", &h[i]);
        
        //base case
        FOR(i,0,17) FOR(j,0,17)
            if(i*a+j*b>h[1] && i*b > h[0])
                dp[1][0][i][j] = min(dp[1][0][i][j], i);
        
        // dp recursion
        FOR(i,1,n-1) FOR(j,0,17) FOR(k,0,17) FOR(x,0,17) FOR(y,0,17)
            if((j+x)*b+k*a > h[i])
                dp[i][j][k][x] = min(dp[i][j][k][x] , dp[i-1][y][j][k] + k);
        
        // ans (handle side case)
        int ans = INF, aa,bb;
        FOR(i,0,17) FOR(j,0,17)
            if(j*a+i*b>h[n-2] && j*b >h[n-1]){
                if(dp[n-2][i][j][0] < ans){
                    ans = dp[n-2][i][j][0];
                    aa = i; bb = j;
                }
            }
        
        printf("%d\n", ans);
        printAns(n-2, aa, bb, 0);
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 2016-03-15
      • 1970-01-01
      • 2020-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-24
      相关资源
      最近更新 更多