不知道是听谁说的,这是一道区间DP的入门题,对于区间思想确实是一道入门题,但是DP方程个人觉得还是很难的,反正我这种蒟蒻肯定是想不出来的。嘤
首先感谢资料:https://blog.csdn.net/libin56842/article/details/9722077
来看一下题目:
有一群人,按照给定输出从1到n排好了顺序,准备一个接一个的出去.此外旁边还一个小黑屋(一个类似于栈的东西),可以用来调整顺序.每个人个屌丝值,如果他第K个上场,屌丝值就为a[i]*(k-1),同过一个小黑屋来调整,是的最后总屌丝值最小
了解题意之后,开始看代码吧,在代码中讲比较好
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
#define INF 99999999
int num[1000],sum[1000],dp[1000][1000];//num[]用来存放输入,sum[]用来预处理前缀和(后面有解释)
//dp[][]用来表示从i到j的最小花费.
int main(void)
{
int t;
cin>>t;
int casen = 1;
while(t --)
{
memset(dp,0,sizeof(dp));
memset(sum,0,sizeof(sum));
memset(num,0,sizeof(num));
int n;
cin>>n;
for(int i = 1 ; i <= n ; i ++)//预处理
{
cin>>num[i];
sum[i] = sum[i-1] + num[i];
}
for(int i = 1 ; i <= n ; i ++)
{
for(int j = i + 1 ; j <= n ; j ++)
{
if(i != j)
dp[i][j] = INF;
else
dp[i][j] = 0;
}
}
//这几层循环是区间DP的板子,相信了解一点区间DP的人都应该很清楚,就不做过多解释了,我们主要来讲DP方程
for(int len = 1 ; len < n ; len ++)
{
for(int i = 1 ; i <= n - len ; i ++)
{
int j = i + len;
for(int k = 1 ; k <= j - i + 1 ; k ++)
dp[i][j] = min(dp[i][j],dp[i+1][i+k-1]+dp[i+k][j]+k*(sum[j]-sum[i+k-1])+num[i]*(k-1));
}
}
/****************************************
dp[i][j]表示从第i个人到第j个人这段区间的最小花费(是只考虑这j-i+1个人,不需要考虑前面有多少人)
那么对于dp[i][j]的第i个人,就有可能第1个上场,也可以第j-i+1个上场。考虑第K个上场
即在i+1之后的K-1个人是率先上场的
{
为什么i+1之后的K-1个人是率先上场的呢?举个例子 对于给定序列 [1] [2] [3] [4] [5] [6] , 我们取 [2] [3] [4] [5] 这个区间
那么 也就是求 dp[2][5] 的值,此时i = 2 , j = 5 ,我们令 k = 3。
根据上述 第[3]之后(包括[3])的 2 个人([4],[5])是率先登场的.
为什么呢:因为[2]第3个出场,所以[2]一定得进去小黑屋(栈--先进后出)此时后面的[3],[4]一定得出场(可以进小黑屋,即调整[3],[4]的出场顺序)但是一定不能让[5]出场,如果[5]出场那么[2]一定不是第3个出场的.
具体解释:[2]首先进入栈,然后[3]如果进入栈,此时栈{[3],[2]},这时候[4]一定得出场了,因为如果此时[4]不出场,那么也就是说[4]进入了栈,那么此时栈{[4],[3],[2]},到[5]的时候,不管[5]是否出场,[2]都不能
第三个出场了,可以慢慢想.
}
, 那么就出现了一个子问题 dp[i+1][i+k-1]表示在第i个人之前上场的
对于第i个人,由于是第k个上场的,那么屌丝值便是a[i]*(k-1)
其余的人是排在第k+1个之后出场的,也就是一个子问题dp[i+k][j],对于这个区间的人,由于排在第k+1(不包含k+1)个之后,所以整体愤怒值要加上k*(sum[j]-sum[i+k-1])
{
为什么整体的愤怒之要加上k*(sum[j]-sum[i+k-1])呢?
dp[i+k][j],对于这个区间的人,由于排在第k+1(包含k+1)个之后
dp[i+k][j]选出来的最优解是没有等k个人的,但是实际上是等了k个人,所以补上来
还是上面的例子:
但是改变k,让k = 2
[2] [3] [4] [5]
2 3 4 5
dp[2][5] = min(dp[2][5],dp[3][3](第i个人之前上场的愤怒值最优解)+dp[4][5](第2个人之后上场的愤怒值最优解)+num[2]*(2 - 1)(第2个人第2个上场的愤怒值)+2*(sum[5]-sum[3])([4]号和[5]号因为等待2个人增长的愤怒值))
dp[3][3] = 0
dp[4][5] = 4*1+5*0 = 4
num[2]*(2-1) = 2
2*(sum[5]-sum[3]) = 4 + 4 + 5 + 5 = 18
以a * b (a代表屌丝值,b代表出场顺序)综合:dp[2][5] = 3*0+2*1+5*2+4*3 = 24 .所以出场顺序为 [2]: 2 [3]: 1 [4]: 4 [5];3
****************************************/
printf("Case #%d: %d\n",casen++,dp[1][n]);
}
}