bzoj4897

4897: [Thu Summer Camp2016]成绩单

Time Limit: 40 Sec   Memory Limit: 512 MB
Submit: 39   Solved: 15
[ Submit][ Status][ Discuss]

Description

期末考试结束了,班主任L老师要将成绩单分发到每位同学手中。L老师共有n份成绩单,按照编号从1到n的顺序叠
放在桌子上,其中编号为i的成绩单分数为w_i。成绩单是按照批次发放的。发放成绩单时,L老师会从当前的一叠
成绩单中抽取连续的一段,让这些同学来领取自己的成绩单。当这批同学领取完毕后,L老师再从剩余的成绩单中
抽取连续的一段,供下一批同学领取。经过若干批次的领取后,成绩单将被全部发放到同学手中。然而,分发成绩
单是一件令人头痛的事情,一方面要照顾同学们的心理情绪,不能让分数相差太远的同学在同一批领取成绩单;另
一方面要考虑时间成本,尽量减少领取成绩单的批次数。对于一个分发成绩单的方案,我们定义其代价为:
其中,k是方案中分发成绩单的批次数,对于第i批分发的成绩单,〖max〗_i是最高分数,〖min〗_i是最低分数。
a,b是给定的评估参数。现在,请你帮助L老师找到代价最小的分发成绩单的方案,并将这个最小的代价告诉L老师
。当然,分发成绩单的批次数k是由你决定的。

Input

第一行包含一个正整数n,表示成绩单的数量。
第二行包含两个非负整数a,b,表示给定的评估参数。
第三行包含n个正整数w_i,表示第i张成绩单上的分数。

Output

仅一个正整数,表示最小的代价是多少。

Sample Input

10
3 1
7 10 9 10 6 7 10 7 1 2

Sample Output

15
【样例数据说明】
第1批:第2至4份成绩单,落差值为1,剩余成绩单为76710712;
第2批:第4份成绩单,落差值为0,剩余成绩单为767712;
第3批:第1至4份成绩单,落差值为1,剩余成绩单为12;
第4批:剩余的2份成绩单,落差值为1。
总代价为4×3+(1^2+0^2+1^2+1^2)×1=15。

HINT

 n<=50, a<=100, b<=10, w_i<=300

Source

[ Submit][ Status][ Discuss]





区间dp。

记dp[i][j]为处理i到j的最小代价。

每次处理dp[i][j]时[i,j]中除了全集的所有区间都已经算完了,只需要挑出一个子序列就可以了。

所以在进行一个子dp,dp1[i][j][k]表示计算到i这个数,选中的最大的元素标号是j,最小的元素标号是k的最小代价(只计算丢掉的区间的代价和)。

每次可以把下一个数添进去,修改j和k。或者把某个区间都不要,此时j和k不变,dp值增加为[j,k]的最小代价,这个已经算过了。

时间复杂度O(n^6),但是跑不满,常数非常小。



#include <stdio.h>
#include <algorithm>

using namespace std;

const int maxn = 60;
int n , a , b;
int w[maxn] , dp[maxn][maxn];
int dp1[maxn][maxn][maxn];
int wmin[maxn][maxn] , wmax[maxn][maxn];
void getdp ( int l , int r ) {
	int i , j , k , o;
	for ( i = l ; i <= r ; i++ ) for ( j = l ; j <= r ; j++ ) for ( k = l ; k <= r ; k++ ) dp1[i][j][k] = 1000000000;
	dp1[l][l][l] = 0;
	for ( i = l ; i <= r ; i++ ) {
		if ( i != l ) dp1[i][i][i] = min ( dp1[i][i][i] , dp[l][i-1] );
		for ( j = l ; j <= r ; j++ ) for ( k = l ; k <= r ; k++ ) {
			if ( dp1[i][j][k] == 1000000000 ) continue;
			dp1[i+1][w[j]>w[i+1]?j:i+1][w[k]<w[i+1]?k:i+1] = min ( dp1[i+1][w[j]>w[i+1]?j:i+1][w[k]<w[i+1]?k:i+1] , dp1[i][j][k] );
			for ( o = i + 1 ; o <= r ; o++ ) {
				dp1[o][j][k] = min ( dp1[o][j][k] , dp1[i][j][k] + dp[i+1][o] );
			}
		}
	}
}
void work () {
	int i , j , k , l , o , x1 , x2 , tmp , u , v;
	scanf ( "%d" , &n );
	scanf ( "%d%d" , &a , &b );
	for ( i = 1 ; i <= n ; i++ ) scanf ( "%d" , &w[i] );
	for ( i = 1 ; i <= n ; i++ ) for ( j = i ; j <= n ; j++ ) {
		if ( j == i ) {
			wmin[i][j] = wmax[i][j] = w[i];
		}
		else {
			wmin[i][j] = min ( wmin[i][j-1] , w[j] );
			wmax[i][j] = max ( wmax[i][j-1] , w[j] );
		}
	}
	for ( l = 1 ; l <= n ; l++ ) {
		for ( i = 1 ; i <= n - l + 1 ; i++ ) {
			j = i + l - 1;
			dp[i][j] = a + b * (wmax[i][j]-wmin[i][j]) * (wmax[i][j]-wmin[i][j]);
			getdp ( i , j );
			for ( k = i ; k <= j ; k++ ) for ( o = i ; o <= j ; o++ ) {
				if ( w[k] < w[o] ) continue;
				dp[i][j] = min ( dp[i][j] , dp1[j][k][o] + a + b * (w[k]-w[o]) * (w[k]-w[o]) );
			}
		}
	}
	printf ( "%d\n" , dp[1][n] );
}
int main () {
	work ();
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值