杀死每个弓箭手所需的最少火球数量?

时间:2016-09-12 07:48:47

标签: algorithm recursion optimization dynamic-programming

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

4 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:0)

一些想法:

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

答案 2 :(得分:0)

首先,最后一名弓箭手可以通过射击相邻的弓箭手以一种可能的方式杀死。杀死这两个后调整阵容。如果有弓箭手还活着,请转到下一步。

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

最左边的射手(LLA)可以通过直接射击或者射击下一射来射击(射击前一射,没有任何一点,因为他已经死了)。所以你的递归函数应该射击LLA,恢复阵列,然后射击与他相邻的弓箭手。

为了减少搜索,你实际上并没有“射击”射手(开始,射击,召唤,恢复,射击,召唤,恢复,结束),但是调用该功能告诉它射击弓箭手作为第一步功能(开始,拍摄,call_to_shoot,call_to_shoot,恢复,结束)。为什么?因为如果拍摄电话是在LLA附近拍摄,那么你就不再“打电话”拍摄LLA了。那道路已经被覆盖了。

额外的优化是,如果需要相同数量的b射击来杀死LLA作为a射击(在数学说ceil(x/b) < floor(x/a + 1)中),那么跳过直接射击他的呼叫( a射击,并立即射击相邻的(b射击)。

你应该预订 - 保持杀死它们所需的最少射击量的值。如果在搜索期间超过此值,则不要再进一步了。

答案 3 :(得分:0)

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

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

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

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

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

请注意,在给定的要求中,i <= 10&amp; a,b,c&lt; = 16 (这个导致我提交2个错误答案,因为小于0而不是小于或等于0)。解决方案也始终存在。

在我们定义状态之后,现在我们可以尝试构建DP解决方案的其他三个组件: 基本情况,递归公式,最终答案查询

基础案例

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

为了杀死第0个射手,我们可以强行将[0,17]火球投掷到第一个射手,同时也是粗暴所有[0,17]火球到 2nd 弓箭手。当然,如果他们可以杀死 0- 1st 弓箭手,那么他们中间的最小值。

//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 <= 16 AND (a,b,c)可以杀死 i-th 射手

回答询问

请注意,我们不能像第一个那样向最后一个射手扔球,所以我们必须像设置基本情况一样进行检查。

答案是a,b <= 16 AND 的所有 DP(n-2,a,b,0)的最小值(a,b, 0)可以杀死 n-2和n-1(最后)弓箭手。

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

状态的实际路径(打印出你必须抛出火球的弓箭手)是一个实施的问题。在这里,我只使用了一个简单的递归,它是 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;
}