P2858 [USACO06FEB]Treats for the Cows G/S 题解

emmmmmm,第二篇文章,多多写文章,好好掌握知识!


前言

原本在educoder上刷题,刷到[粉刷匠]一题,使用区间DP来做的。自己之前曾经小部分刷过背包DP的题目,对于区间DP还是知之甚少。在稍微了解了区间DP之后,尝试刷题巩固。

这一题来自   Luogu  P2858 [USACO06FEB]Treats for the Cows G/S 


一、区间DP

OI WIKI -区间DP 

关于动态规划,其实说白了,就是一种递推。当我们解决大问题的时候,先把它 分解 为若干个子问题,再把它 合并 成当前所需的结果。有点类似于递归的感觉,而递归会重复计算,普通dp 与记忆化搜索不会。

而区间dp,就是这类问题中的一小类。在我们的 分解 操作时,它需要取任意区间的子问题结果。这类问题就叫做区间dp。

对于大多数问题,区间dp 的架构像这样:dp(i,j) 被拆分为若干个dp(l,r)​ (其中 i<=l <= r <=j),通过这些小区间计算出来的dp 值推算dp(i,j)​。而dp 的两个下标代表一个区间的左端点与右端点。

通常,我们在做此类问题时,是由小区间推到为大区间。所以,我们要先枚举小区间,再枚举大区间,也就是 枚举长度 。

                                                                                        ————引自洛谷BreakPlus' Blog

区间DP经典模板:

memset(dp,0,sizeof(dp))//初始dp数组
for(int len=2;len<=n;++len){//枚举区间长度
    for(int i=1;i<n;++i){//枚举区间的起点
        int j=i+len-1;//根据起点和长度得出终点
        if(j>n) break;//符合条件的终点
        for(int k=i;k<=j;++k)//枚举最优分割点
            dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);//状态转移方程
    }
}
//引自mumei314的博客

记住边界条件是len=1时的情况,从小区间推到大区间,动态转移方程由实际的题意给出。

二、P2858 [USACO06FEB]Treats for the Cows G/S 

传送门

1.题目描述

FJ has purchased N (1 <= N <= 2000) yummy treats for the cows who get money for giving vast amounts of milk. FJ sells one treat per day and wants to maximize the money he receives over a given period time.

The treats are interesting for many reasons:The treats are numbered 1..N and stored sequentially in single file in a long box that is open at both ends. On any day, FJ can retrieve one treat from either end of his stash of treats.Like fine wines and delicious cheeses, the treats improve with age and command greater prices.The treats are not uniform: some are better and have higher intrinsic value. Treat i has value v(i) (1 <= v(i) <= 1000).Cows pay more for treats that have aged longer: a cow will pay v(i)*a for a treat of age a.Given the values v(i) of each of the treats lined up in order of the index i in their box, what is the greatest value FJ can receive for them if he orders their sale optimally?

The first treat is sold on day 1 and has age a=1. Each subsequent day increases the age by 1.

约翰经常给产奶量高的奶牛发特殊津贴,于是很快奶牛们拥有了大笔不知该怎么花的钱.为此,约翰购置了N(1≤N≤2000)份美味的零食来卖给奶牛们.每天约翰售出一份零食.当然约翰希望这些零食全部售出后能得到最大的收益.这些零食有以下这些有趣的特性:

•零食按照1..N编号,它们被排成一列放在一个很长的盒子里.盒子的两端都有开口,约翰每

天可以从盒子的任一端取出最外面的一个.

•与美酒与好吃的奶酪相似,这些零食储存得越久就越好吃.当然,这样约翰就可以把它们卖出更高的价钱.

•每份零食的初始价值不一定相同.约翰进货时,第i份零食的初始价值为Vi(1≤Vi≤1000).

•第i份零食如果在被买进后的第a天出售,则它的售价是vi×a.

Vi的是从盒子顶端往下的第i份零食的初始价值.约翰告诉了你所有零食的初始价值,并希望你能帮他计算一下,在这些零食全被卖出后,他最多能得到多少钱.

2.输入格式

Line 1: A single integer, N

Lines 2..N+1: Line i+1 contains the value of treat v(i)

3.输出格式

Line 1: The maximum revenue FJ can achieve by selling the treats

4.输入输出样例

输入 #1                                                                        输出 #1

5                                                43
1
3
1
5
2

三、算法思考

1.五大三粗从贪心开始。

每次只能从头和尾取出元素,也即每次都只有两个选择——头出、尾出。那么贪心是否能行呢?

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<limits.h>
#include<iomanip>

using namespace std;

int n;
int v[2005];
int head,tail;
int day=0;
int ans=0;

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	head=1;tail=n;
	for(int i=1;i<=n;++i){
		cin>>v[i];
	}
	while(tail>=head){
		day++;
		if(v[head]<v[tail]){
			ans+=v[head]*day;
			head++;
		}else{
			ans+=v[tail]*day;
			tail--;
		}
	}
	cout<<ans;
	return 0;
} 

好嘛,提交27/100分,妥妥的WA了。

我们来看看贪心为什么不可以——什么时候可以用贪心?

贪心算法的适用

贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是局部最优解能决定全局最优解。简单地说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解

算法思想:从问题的某一个初始解出发逐步逼近给定的目标,以尽可能快的地求得更好的解。当达到某算法中的某一步不能再继续前进时,算法停止。

贪心算法的适用范围:

贪心算法并不能总求得问题的整体最优解。但对于某些问题,却总能求得整体最优解,这要看问题时什么了。只要能满足贪心算法的两个性质:贪心选择性质和最优子结构性质,贪心算法就可以出色地求出问题的整体最优解。即使某些问题,贪心算法不能求得整体的最优解,贪心算法也能求出大概的整体最优解。如果你的要求不是太高,贪心算法是一个很好的选择。最优子结构性质是比较容易看出来的,但是贪心选择性质就没那么容易了,这个时候需要证明。证明往往使用数学归纳法。(CSDN博主「韦盛江」

本题不适用贪心 

原因就在于本题目的具有后效性。

构造一个简单的例子:13个数据——9 9 9 9 9 9 1 1 1 1 1 1 10 

贪心明显得出了错误的答案,具体请读者实操。

2.经典的区间DP

为得到(1,n)整个区间上卖出方案所得到金钱的最大值,可以从区间(2,n)和(1,n-1)得到,同样的以此类推,最终可将方案归到最小长度的区间。我们找到了递归的出口。

——由小区间的最优值得到大区间的最优值,不就是区间DP所能解决的问题吗。

还记得我们上方的区间DP板子吗,O(n^3)的复杂度,内部有一层对断点K的遍历,然而本题已经说明取数只能从头或者尾部选择,这就少了对K的遍历。

根据题义某一区间的最优值来自于len-1的子区间,我们可以写出状态转移方程。

dp[left][right]=max(dp[left+1][right]+v[left]*(n-len+1),dp[left][right-1]+v[right]*(n-len+1));

(emmm个人习惯/癖好,简单来说right,left可用i,j代替。/手动狗头)

AC代码如下:

//区间DP 
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<limits.h>
#include<iomanip>
using namespace std;

int n,v[2005];
int dp[2005][2005];
//dp[i][j]表示从区间i到j所能得到的局部最优消费值 

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>v[i];
		dp[i][i]=v[i]*n;//初始化dp 
	} 
	//区间dp板子
	for(int len=2;len<=n;++len){
		for(int left=1;left<=n;++left){//i为区间左端点 
			int right=left+len-1;
			if(right>n)continue;
			dp[left][right]=max(dp[left+1][right]+v[left]*(n-len+1),dp[left][right-1]+v[right]*(n-len+1));
		}
	} 	
	cout<<dp[1][n];
	return 0;
} 

三、结语

区间DP的题目只是刚刚接触到,大多都是板子题,需要大量做题,找到适用区间DP以及合理的转移方程。此外,各种优化方法在初步掌握之后,也需要了解熟练运用。

对于这一题,Luogu官网题解,点击这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值