hihocoder 1532 最美和弦 (dp)

描述

某个夜晚,Bob将他弹奏的钢琴曲录下来发给Jack,Jack感动之余决定用吉他为他伴奏。

我们可以用一个整数表示一个音符的音高,并可认为Bob弹奏的曲子是由3N个整数构成的一个序列。其中每个整数的取值范围是[-200, 200]。

Jack共弹奏 N 个和弦,每个和弦由三个音符组成。Jack可以自行决定和弦的第一个音符,其后的两个音符由第一个音符与和弦种类所决定。Jack共弹奏两种和弦:大三和弦与小三和弦。假设Jack决定某个和弦的第一个音符是 x,那么对于大三和弦,余下两个音符依序是 x+4和 x+7;对于小三和弦,余下两个音符依序是x+3和x+7。两个和弦相同,当且仅当其对应位置的三个音符都相同。其中每个和弦的第一个音符x的取值范围也是[-200, 200]。

Jack很懒,一旦决定弹奏某个和弦后,便不愿意更换和弦。即如果他开始弹奏1,5,8这个和弦,他将不停重复1,5,8,1,5,8,1,5,8……Bob觉得这样过于单调,于是Jack妥协:他表示愿意更换和弦,但最多更换K次。最开始选择和弦不计在更换次数内。

我们用不和谐值衡量乐曲与伴奏之间的契合程度。记某时刻Bob弹奏音符的音高为a,Jack弹奏音符的音高为b,则该点的不和谐值为|a-b|。整首乐曲的不和谐值等于这3N个不和谐值之和。

Jack希望选取最美的一组和弦,使得整首乐曲的不和谐值达到最小。你需要输出这个最小值。

 

输入

第一行两个正整数 N (≤1000), K (≤20).

第二行3N个整数(取值范围[-200, 200])为Bob的曲谱。

 

输出

一个整数,为乐曲最小不和谐值。

 

样例输入

3 1
-1 3 6 4 7 11 21 26 28

 

样例输出

15

 

思路

基础的 dp 问题。

我们设 dp[i][j][s][k] 代表第 i 个和弦,在已经更换 j 次的情况下,使用 s 和弦,起始音符为 k 时所得到的最小不和谐值。

对于每一个新的和弦,我们可以选择更换或者不更换当前正在演奏的和弦。

于是便有状态转移方程:

$dp[i][j][s][k]=\min(val[j-1],dp[i-1][j][s][k])+getsum(i,k,s)$

其中 val[j-1] 代表更换 j-1 次和弦所得到的最小不和谐值,它也就等于 dp[i-1][j-1][0~1][-200~200] 中的最小值。

getsum 代表选择 s 和弦, k 为起始音符所计算的不和谐值。

 

AC 代码

#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define inf 0x3f3f3f

int a[1005][3];
int dp[1005][25][2][405];
int val[25];

inline int getsum(int i,int k,int s)
{
    return abs(k-a[i][0])+abs(k+3+s-a[i][1])+abs(k+7-a[i][2]);
}

int main()
{
    int n,ks;
    while(~scanf("%d%d",&n,&ks))
    {
        for(int i=0; i<n; i++)
            for(int j=0; j<3; j++)
                scanf("%d",&a[i][j]);
        for(int i=0; i<n; i++)
        {
            for(int k=-200; k<=200; k++)
            {
                for(int l=0; l<=ks; l++)
                {
                    for(int s=0; s<2; s++)
                    {
                        dp[i][l][s][k+200]=dp[max(i-1,0)][l][s][k+200]+getsum(i,k,s);
                        if(l)dp[i][l][s][k+200]=min(val[l-1]+getsum(i,k,s),dp[i][l][s][k+200]);
                    }
                }
            }
            memset(val,inf,sizeof(val));
            for(int k=-200; k<=200; k++)
                for(int l=0; l<=ks; l++)
                    val[l]=min(val[l],min(dp[i][l][0][k+200],dp[i][l][1][k+200]));
        }
        printf("%d\n",val[ks]);
    }
    return 0;
}