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.
现在我编写了一个递归函数,该函数获取了弓箭手当前健康状况的数组并生成每次可能的攻击,直到所有弓箭手都死亡并返回最小值。
但这种方法太慢了,如何有效地解决这个问题?
注意:我不一定对优化自己的解决方案感兴趣,但对任何其他可能更快的解决方案持开放态度。
答案 0 :(得分:2)
关键是要杀死最左边的弓箭手,你需要通过火球或者在他旁边或者射手旁边。
答案 1 :(得分:0)
一些想法:
答案 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;
}