BZOJ-1587|前缀和 预处理 dp||叶子合并leaves
叶子合并leaves
Description
在一个美丽的秋天,丽丽每天都经过的花园小巷落满了树叶,她决定把树叶堆成K堆,小巷是笔直的 共有N片树叶(树叶排列也是笔直的),每片树叶都有一个重量值,并且每两片想邻的树叶之间的距离都是1 现把所有的树叶按从左到右的顺序进行编号,编号为1..N。丽丽移动每片树叶所消耗能量等于这片树叶的重量 乘以移动的距离,丽丽决定分K天完成,每天堆一堆,并且规定只能把树叶往左移动,因为丽丽每天都是从右往左 经过小巷的。求丽丽完成任务所消耗的最少能量。
Input
输入的第一行为两个用空格隔开的正整数N和K。后面有N行 每行一个正整数表示叶子的重量(第i+1行表示第i片树叶的重量)
Output
输出为一个整数,表示把树叶堆成K堆所消耗的最少体力。
Sample Input 1
5 2
1
2
3
4
5
Sample Output 1
13
Hint
N在(0,1001)
K在(0,11)
每片树叶的重量(0,1001)
思路:
前缀和: pre[i] 前i个叶子的重量和
预处理:sum[i][j] 前i个叶子移动到第j个位置的总花费
dp递推: dp[i][j] 前i个叶子分成k堆的最小值 == min(前j个分成一堆 + j~i个分成的一堆 共k(k~K)堆)的最小值i和j
AC代码:
#include<bits/stdc++.h>
using namespace std;
/*
前缀和: pre[i] 前i个叶子的重量和
预处理:sum[i][j] 前i个叶子移动到第j个位置的总花费
dp递推: dp[i][j] 前i个叶子分成k堆的最小值 == min(前j个分成一堆 + j~i个分成的一堆 共k(k~K)堆)的最小值i和j
*/
const int maxn = 1010;
int n,K;
int a[maxn];
int pre[maxn];
int sum[maxn][maxn];
int dp[maxn][maxn];
int main(){
scanf("%d%d",&n,&K);
for(int i=n;i>=1;i--){ //从右往左输入以便后面正序遍历
scanf("%d",&a[i]);
}
pre[0] = 0;
for(int i=1;i<=n;i++){
pre[i] = pre[i-1] + a[i];//前缀和
}
//预处理sum[i][j]
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
if(i==j) sum[i][j] = sum[i-1][j-1] + pre[i-1];//当i == j,等于 前i-1个叶子(加上第i个叶子也一样 但是j>i的 所以分类讨论递推式)移动到j-1个位置的消耗 + 前i-1个叶子一起移动到第j个位置(前缀和pre[i-1])
else sum[i][j] = sum[i][j-1] + pre[i];//等于 前i-1个叶子移动到j-1个位置的消耗 + 前i个叶子一起移动到第j个位置(前缀和pre[i])
}
}
//初始化dp
memset(dp,0x3f3f3f3f,sizeof(dp));
for(int i=1;i<=n;i++) dp[i][1] = sum[i][i]; //只能分成j==1堆时,dp[i][j]等于把前i堆移动到最后的i个叶子上(sum[i][i])
for(int k=2;k<=K;k++){//分成k堆
for(int i=1;i<=n;i++){//前i个,到n
for(int j=1;j<i;j++){//j堆,最多到i堆
dp[i][k] = min(dp[i][k],dp[j][k-1] + sum[i][i] - sum[j][i]);//状态转移方程: 前i个分成k堆 等于 min(前j个分成k-1堆 加上 第j+1~第i个分成另外一堆)的最小花费
}
}
}
printf("%d\n",dp[n][K]);
return 0;
}