{ }codelper
동적 계획법P0267

콤보가 쌓이는 리듬 게임

리듬 게임의 한 곡에 노트 n개가 차례로 나온다. i번째 노트의 점수는 a_i이고, 음수일 수도 있다. 노트마다 칠지 그냥 보낼지 고른다. 바로 앞 노트도 쳤다면 이번 노트는 콤보를 잇는다. 콤보에서 첫째로 친 노트는 점수의 1배, 둘째로 친 노트는 2배, 셋째와 그 뒤로 친 노트는 모두 3배를 얻는다. 노트를 하나 보내면 콤보가 끊기고, 그다음에 치는 노트는 다시 콤보의 첫째(1배)가 된다. 첫 노트는 늘 콤보의 첫째다. 얻는 점수는 친 노트들이 얻은 점수의 합이다. 하나도 치지 않으면 0점이다. 첫 줄에 n, 둘째 줄에 a_1 … a_n이 빈칸으로 띄워 주어진다. 얻을 수 있는 가장 큰 점수를 출력하라.

입력
1 ≤ n ≤ 10,000, -10,000 ≤ a_i ≤ 10,000 (정수)
출력
가장 큰 점수 하나(0 이상의 정수)
예시 1 · stdin
5
-1 5 3 -10 4
stdout
22
예시 2 · stdin
3
-5 -2 -7
stdout
0
main.py
입력(stdin)

실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

음수 노트라도 쳐서 콤보를 이어야 뒤 노트가 3배를 받을 수 있어서(5 5 -1 5 5는 모두 치면 42점, -1만 보내면 30점), 0보다 큰 노트만 치는 욕심 풀이는 틀려요.
노트 하나를 지날 때마다 "방금 보냈다·콤보 첫째·둘째·셋째 이후" 네 상태의 가장 좋은 점수를 이어 가는 동적 계획법이면 n번 만에 끝나요.
첫째 함정은 상태를 바꾸는 차례예요.
새 값은 모두 이전 노트의 값으로 계산해야 해서, 하나씩 바로 덮어쓰면 c2가 이미 바뀐 c1을 써요.
Python은 skip, c1, c2, c3 = …, JS·TS는 배열 구조 분해 [skip, c1, c2, c3] = [...], C#은 튜플 (skip, c1, c2, c3) = (...)로 한꺼번에 바꿀 수 있어요.
Java·C·크레이콘에는 이런 동시 대입이 없어서 새 값을 임시 변수에 먼저 구해 두고 옮겨요.
C++는 std::tie(skip, c1, c2, c3) = std::make_tuple(...)로 한꺼번에 바꿀 수도 있지만, 임시 변수가 더 흔해요.
둘째 함정은 아직 닿을 수 없는 상태의 처음 값이에요.
첫 노트 앞에서 c1·c2·c3를 0으로 두면 첫 노트부터 2배·3배를 받은 셈이 돼요.
Python float("-inf"), JS·TS -Infinity처럼 아주 작은 값에서 시작해요.
Java·C#·C·C++의 정수에는 무한대가 없고 Integer.MIN_VALUE(int.MinValue, INT_MIN)에 음수 점수를 더하면 넘쳐서 Java·C#에서는 큰 양수가 되고 C·C++에서는 정의되지 않은 동작이니, 그 절반처럼 여유 있는 값을 써요.
크레이콘에는 이런 상수가 없어서 -1조 같은 수를 직접 둬요.
셋째로, 마지막 노트를 보내는 경우와 하나도 치지 않는 경우(0점)까지 네 상태 모두에서 가장 큰 값을 답으로 골라요.

Pythonmain.py · 12 줄
n = int(input())a = list(map(int, input().split())) # 노트 하나를 지날 때마다 상태 넷의 가장 좋은 점수를 이어 간다#   skip: 이 노트를 보냈다(콤보 없음)#   c1, c2: 이 노트가 콤보의 첫째·둘째#   c3: 이 노트가 콤보의 셋째 이후(3배)NONE = float("-inf")  # 아직 닿을 수 없는 상태skip, c1, c2, c3 = 0, NONE, NONE, NONEfor x in a:    skip, c1, c2, c3 = max(skip, c1, c2, c3), skip + x, c1 + 2 * x, max(c2, c3) + 3 * xprint(max(skip, c1, c2, c3))
JavaScriptmain.js · 9 줄
const nums = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = nums[0];// skip: 이 노트를 보냄, c1·c2: 콤보 첫째·둘째, c3: 셋째 이후(3배). -Infinity는 아직 닿을 수 없는 상태let skip = 0, c1 = -Infinity, c2 = -Infinity, c3 = -Infinity;for (let i = 1; i <= n; i++) {  const x = nums[i];  [skip, c1, c2, c3] = [Math.max(skip, c1, c2, c3), skip + x, c1 + 2 * x, Math.max(c2, c3) + 3 * x];}console.log(Math.max(skip, c1, c2, c3));
TypeScriptmain.ts · 10 줄
import { readFileSync } from "node:fs";const nums: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n: number = nums[0];// skip: 이 노트를 보냄, c1·c2: 콤보 첫째·둘째, c3: 셋째 이후(3배). -Infinity는 아직 닿을 수 없는 상태let skip = 0, c1 = -Infinity, c2 = -Infinity, c3 = -Infinity;for (let i = 1; i <= n; i++) {  const x: number = nums[i];  [skip, c1, c2, c3] = [Math.max(skip, c1, c2, c3), skip + x, c1 + 2 * x, Math.max(c2, c3) + 3 * x];}console.log(Math.max(skip, c1, c2, c3));
JavaMain.java · 26 줄
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.StringTokenizer; public class Main {    // 아직 닿을 수 없는 상태: 어떤 점수보다도 훨씬 작고, 3배 점수를 더해도 넘치지 않는 값    static final int NONE = Integer.MIN_VALUE / 2;     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(in.readLine().trim());        StringTokenizer st = new StringTokenizer(in.readLine());        // skip: 이 노트를 보냄, c1·c2: 콤보 첫째·둘째, c3: 셋째 이후(3배)        int skip = 0, c1 = NONE, c2 = NONE, c3 = NONE;        for (int i = 0; i < n; i++) {            int x = Integer.parseInt(st.nextToken());            int nSkip = Math.max(Math.max(skip, c1), Math.max(c2, c3));            int n1 = skip + x;            int n2 = c1 + 2 * x;            int n3 = Math.max(c2, c3) + 3 * x;            skip = nSkip; c1 = n1; c2 = n2; c3 = n3;        }        System.out.println(Math.max(Math.max(skip, c1), Math.max(c2, c3)));    }}
C#Main.cs · 21 줄
using System;using System.Linq; class Program{    // 아직 닿을 수 없는 상태: 어떤 점수보다도 훨씬 작고, 3배 점수를 더해도 넘치지 않는 값    const int None = int.MinValue / 2;     static void Main()    {        int n = int.Parse(Console.ReadLine());        int[] a = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();        // skip: 이 노트를 보냄, c1·c2: 콤보 첫째·둘째, c3: 셋째 이후(3배)        int skip = 0, c1 = None, c2 = None, c3 = None;        foreach (int x in a)        {            (skip, c1, c2, c3) = (Math.Max(Math.Max(skip, c1), Math.Max(c2, c3)), skip + x, c1 + 2 * x, Math.Max(c2, c3) + 3 * x);        }        Console.WriteLine(Math.Max(Math.Max(skip, c1), Math.Max(c2, c3)));    }}
C++main.cpp · 20 줄
#include <algorithm>#include <climits>#include <iostream> int main() {    // 아직 닿을 수 없는 상태: 어떤 점수보다도 훨씬 작고, 3배 점수를 더해도 넘치지 않는 값    const int NONE = INT_MIN / 2;    int n;    std::cin >> n;    // skip: 이 노트를 보냄, c1·c2: 콤보 첫째·둘째, c3: 셋째 이후(3배)    int skip = 0, c1 = NONE, c2 = NONE, c3 = NONE;    for (int i = 0; i < n; i++) {        int x;        std::cin >> x;        int ns = std::max({skip, c1, c2, c3});        int n1 = skip + x, n2 = c1 + 2 * x, n3 = std::max(c2, c3) + 3 * x;        skip = ns; c1 = n1; c2 = n2; c3 = n3;    }    std::cout << std::max({skip, c1, c2, c3}) << '\n';}
Cmain.c · 23 줄
#include <limits.h>#include <stdio.h> /* 아직 닿을 수 없는 상태: 어떤 점수보다도 훨씬 작고, 3배 점수를 더해도 넘치지 않는 값 */#define NONE (INT_MIN / 2) static int max2(int a, int b) { return a > b ? a : b; } int main(void) {    int n;    scanf("%d", &n);    /* skip: 이 노트를 보냄, c1·c2: 콤보 첫째·둘째, c3: 셋째 이후(3배) */    int skip = 0, c1 = NONE, c2 = NONE, c3 = NONE;    for (int i = 0; i < n; i++) {        int x;        scanf("%d", &x);        int ns = max2(max2(skip, c1), max2(c2, c3));        int n1 = skip + x, n2 = c1 + 2 * x, n3 = max2(c2, c3) + 3 * x;        skip = ns; c1 = n1; c2 = n2; c3 = n3;    }    printf("%d\n", max2(max2(skip, c1), max2(c2, c3)));    return 0;}
Craykonmain.cray · 22 줄
n = 콘솔.줄읽기().수로변환();a = 콘솔.수들읽기();// 아직 닿을 수 없는 상태: 어떤 점수보다도 훨씬 작은 값못감 = -1000000000000;// skip: 이 노트를 보냄, c1·c2: 콤보 첫째·둘째, c3: 셋째 이후(3배)skip = 0;c1 = 못감;c2 = 못감;c3 = 못감;각각 (x 에서 a){    // 새 값은 모두 이전 값으로 계산한다: 먼저 따로 구해 두고 한꺼번에 바꾼다    새skip = 수학.큰수(수학.큰수(skip, c1), 수학.큰수(c2, c3));    새c1 = skip + x;    새c2 = c1 + 2 * x;    새c3 = 수학.큰수(c2, c3) + 3 * x;    skip = 새skip;    c1 = 새c1;    c2 = 새c2;    c3 = 새c3;}콘솔.쓰기(수학.큰수(수학.큰수(skip, c1), 수학.큰수(c2, c3)));