在TechFlow学长的公众号里发现一道挺有意思的CF算法题,现在利用学长的思路学习一下
题目链接:https://codeforces.com/contest/1418/problem/C
题意
这道题的题意也很有意思,背景也是游戏。说是有一天你和你的基友一起在家打游戏,这个游戏一共有n个boss。这些boss的难度不同,有些boss简单,有些boss困难。你的技术要比基友的好一些,你们两人轮流打boss。
游戏规定每次进行游戏最少打1个boss,最多打两个boss。由于你的实力更好,你可以战胜所有的boss。但是你的基友比较菜,只能打得过简单的boss,如果碰上hard模式的boss就只能氪金。基友的钱也是钱,你们希望在尽量少氪金的前提下把游戏通关。现在已知所有boss的难易情况并且基友先开始游戏,请问在最佳策略下,最少需要氪金多少次?
样例
首先给定一个数字t,表示测试数据组数。对于每组数据,给定一个数字n,表示boss的数量。接着给定n个0或者1的整数,0表示boss是简单模式,1表示是困难模式。要求返回一个数字,即最少的氪金次数。其中
input:
8
1 0 1 1 0 1 1 1
output:
2
样例解释
基友先杀1和2两个boss,氪金一次。
“我”杀3和4号boss
基友杀5号boss
“我”杀6和7号boss
基友击杀8号boss,氪金一次,总共氪金两次。
题解
这道题我们最先想到的可能就是贪心,比如我们可以想到一种贪心策略,就是每次基友杀怪的时候先杀1个,然后看第二个是0还是1,如果是0的则一起杀了,否则不杀留给“我”。
我们可以用之前介绍过的等价判断法来判断一下这个贪心策略可不可行,对于这道题而言,贪心的本质是让氪金的次数最少。所以当基友的第二个怪是0的时候,杀和不杀对于当前的氪金次数来说是没有影响的。但是对于后面的局面是会有影响的,并且可能会出现不同的结果。
比如我们可以找到一个例子10011,基友杀不杀第二个怪,直接影响后面的结果。如果基友杀了,那么不论“我”怎么选,基友都必须要至少再氪金一次。如果基友不杀,那么“我”杀第二个怪,基友再杀第三个怪,最后两个boss都交给“我”,那么基友全局只需要氪金一次。所以贪心算法不可行。
动态规划
如果你熟悉动态规划的话,那么几乎可以发现这是一道经典的动态规划问题。对于每一个怪来说,它都有两种状态,分别是被基友杀或者是被“我”杀。我们用0和1来分别表示,0表示被基友杀,1表示被“我”杀。一共有n个怪,所以我们可以用一个n * 2的数组来记录所有怪的状态。
对于第i个怪而言,如果它是被“我”杀的,那么它可以由基友杀了i-1或者是i-2个怪的状态转移得到。比如如果从基友杀了i-1转移得到,说明“我”杀了i,否则说明“我”不仅杀了i,还杀了i-1。同理i被基友杀的情况也是一样,所以这个状态转移方程就非常明显了。
# 学长的Python解法,注释满满
import sys
t = int(input())
for _ in range(t):
n = int(input())
arr = list(map(int, input().split(' ')))
dp = [[sys.maxsize, sys.maxsize] for _ in range(n+2)]
dp[0][1] = 0
for i in range(1, n+1):
if i > 1:
# 如果i > 1,那么说明可以杀两个
# 0表示基友杀怪的情况,基友可以杀1个从i-1转移得到,也可以杀2个从i-2转移得到
# 需要加上氪金的次数
dp[i][0] = min(dp[i-1][1] + arr[i-1], dp[i-2][1] + arr[i-2] + arr[i-1])
# 我杀怪不用氪金,直接赋值即可
dp[i][1] = min(dp[i-1][0], dp[i-2][0])
else:
# i=1,那么只能杀一个
dp[i][0] = dp[i-1][1] + arr[i-1]
dp[i][1] = dp[i-1][0]
print(min(dp[n][0], dp[n][1]))
// 蒟蒻C++解法
// Author : RioTian
// Time : 20/11/07
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int main() {
// freopen("in.txt", "r", stdin);
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
int x, ans = 0, sum = 0;
n--;
cin >> ans;
while (n--) {
cin >> x;
if (x)
sum++;
else
ans += sum / 3, sum = 0;
}
cout << ans + sum / 3 << endl;
}
}
最后,这道题非常的基础,可以说是动态规划的基础问题了。如果对动态规划这个概念不是很熟悉的话,非常建议动手做一做,加深一下印象。