北大ACM——3186,Treats For The Cows(DP)

题意:有n个treats放在一个长长的箱子里,只有箱子两端有开口,第i个treat的价值为v[i],每个treat可获利v[i]*a,其中a为treat的“年龄”,显然“年龄”越大越值钱。每天只能卖出一个treat,设第一天a=1,随天数递增1,求能获得的最大价值。

分析:这道题一开始以为好像很简单,觉得只要用一维的dp就够了,当时的状态是这样子的:
dp[i]:表示前i天所能获得的最大价值;
后来百思不得其解,想不出dp[i]的转移方程,就改成:
dp[i]:表示在第i天卖出某treat最划算。
方程为:dp[i]=min(v[front],v[end]),用sum求和。
似乎对了有几个样例,觉得没毛病,但提交了几遍都WA。
方程中使用了min函数有事先证明过,先卖v[i]小的,再卖v[i]大的更划算,但其实现在一想,这种做法应该不属于DP,而是属于贪心,显然,贪心在这里并不能在所有情况都得出最优解,事实上,这个dp也写得有点假呀,dp[i]跟前后的元素一点联系也没有。。。(所以以后如果再有这种情况,就该怀疑自己是不是错了,我想,dp应该是一个与前或后都有紧密联系的算法,而非独立的)

于是,在网上看了大佬的博客,方发现,这道题应当用二维数组来做,属于区间dp
博客链接:https://blog.csdn.net/power721/article/details/5803619

思路:
dp[i,j]:表示从第i个到第j个treat能够获得的最大价值;
dp[i,j]=max(dp[i+1,j]+v[i]×(n+i-j),dp[i,j-1]+v[j]×(n+i-j));

解释一下为什么是n+i-j,这是表示“年龄”,举两个特例就可以确定了,比如i=1,j=n,那么“年龄”a=n+1-n=1,再如i=2,j=n-1,a=n+2-(n-1)=3。
最终答案:dp[1,n](想想,这样的话那么循坏的方向应该怎么决定呢)
边界:
当i>j时,是没有什么意义的,因此设置循坏时可以跳过这些;
当i=j=k时,即dp[k,k],这个时候表示只剩下一个treat了,而且已经到了最后一天了,因此应将dp[k,k]=v[k]×n

代码如下:

#include<cstdio>
#include<algorithm>
using namespace std;
long long v[2005],dp[2005][2005];
int main()
{
	int n,i,j;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	    scanf("%d",&v[i]);
	for(i=n;i>=1;i--)
	{
		for(j=i;j<=n;j++)
		{
			if(i==j)
			    dp[i][j]=v[i]*n;
			else
			    dp[i][j]=max(dp[i+1][j]+v[i]*(n+i-j),dp[i][j-1]+v[j]*(n+i-j));
		}
	}
	printf("%lld\n",dp[1][n]);
	return 0;
}

小结:
1.这道题较有收获的是,让我这个小白知道,dp[i,j]的i,j是可以表示同一个意义的,而就目前个人的水平来理解,区间dp这一类题,应该都有这个特点,就是它的i,j意义是相同的;
2.后来自己有在推,不太清楚这个“年龄”怎么表示,以后如有这种情况,不要死脑筋,举两个特例就ok啦;
3.边界很重要,做DP题时要考虑好,这道题就是,此外,还要注重循环的方向,可以从最终答案的形式去确定。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值