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;
} 
posted @ 2019-04-19 21:53  fishers  阅读(262)  评论(0编辑  收藏  举报