题意
A和B两人玩通关游戏,每个关卡分为难(a[i]=1)和简单(a[i]=0),A可以通过任意难度的关卡,B只能通过难度为0的关卡,但是B在遇到难度为1的关卡的时候,可以选择跳跃一次,A和B轮流交替通关,每一轮两人最少可以通关一关,最多通关两关,B始终第一个开始,问:最终通关时,B进行跳跃的最少次数
思路
由于是需要求一个最优解,同时这个最优解由AB两人的选择控制,为了维护这个结果最优,需要每个回合两人的选择都是最优的,同时A的选择还会影响到B的选择。所以我们考虑动态规划进行求解。
求解动态规划的四个步骤
一、确定状态
设数组dp[i][0]表示第i轮中 B消耗的跳跃次数, dp[i][1]表示第i轮中,A消耗的跳跃次数。
两个意识:
1.最后一步
即最终的一个状态。本题中的最后一个状态应该是min( dp[n][0], dp[n][1] )
2.子问题
由于我们的每一轮的最终答案是由上一轮的结果来决定,所以我们可以将每个问题化解成它的子问题,在本题中,我们考虑走到最后一关的最优解,则可以将问题拆解为最后一关的最优解+前面通过的所有关卡的最优解,不断拆分,就可以将问题简化为我们已知的问题。
二、 状态转移方程
对于dp问题,最关键的就是推出该问题的状态转移方程,在本题中,我们设数组dp[i][0]表示第i轮中 B消耗的跳跃次数,
dp[i][1]表示第i轮中,A消耗的跳跃次数。由于AB两人在自己的回合可以选择通过1关或者2关,所以我们需要每一轮都选择二者中消耗跳跃步数最少的一个决策,以确保最后的决策是最优的。
对于第i回合的B来说:
选择通1关:
dp[i][0] = dp[i-1][1] + a[i];
选择通2关:因为是通两关,所以要加上a[i-1]和当前的关卡a[i]
dp[i][0] = dp[i-2][1] + a[i-1] + a[i]
第i轮B的最优策略为:dp[i][0] = min( dp[i-1][1] + a[i],dp[i-2][1] + a[i-1] + a[i] )
对于第i回合的A来说
选择通1关:
dp[i][1] = dp[i-1][0]
选择通2关:
dp[i][1] = dp[i-2][0]
第i轮A的最优策略为:dp[i][1] = min( dp[i-1][0], dp[i-2][0])
三、初始条件和边界情况
边界情况在本题中不需要考虑。初始条件为:
第一轮:dp[1][0] = a[1] dp[1][1] = 无限大(因为B始终走第一轮)
第二轮:dp[2][0] = a[1] + a[2] dp[2][1] = dp[1][0]
四、计算顺序
一般来说按照从小到大,从左往右,或者对于二维数组来说一行一行的进行计算。
AC代码:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <string>
#include <math.h>
#include <set>
#include <map>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 2e5 + 5;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int a[maxn];
int dp[maxn][2];
int main() {
int t = 0;
cin >> t;
while(t--){
int n;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
dp[1][0] = a[1];
dp[1][1] = INF;
dp[2][0] = a[1] + a[2]; //前两个数
dp[2][1] = dp[1][0];
for(int i = 3; i <= n; i++){
dp[i][0] = min(dp[i-1][1] + a[i],dp[i-2][1] + a[i] + a[i-1] + a[i]);
dp[i][1] = min(dp[i-1][0],dp[i-2][0]);
}
cout << min(dp[n][0],dp[n][1]) << endl;
}
return 0;
}