Codeforce演算法題 | 你能想出解法,讓你的基友少氪金嗎?

RioTian發表於2020-11-07

在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表示是困難模式。要求返回一個數字,即最少的氪金次數。其中

\[n≤2⋅10^5 \]

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;
    }
}

最後,這道題非常的基礎,可以說是動態規劃的基礎問題了。如果對動態規劃這個概念不是很熟悉的話,非常建議動手做一做,加深一下印象。

相關文章