这道题目,我需要和另外一道题目来进行比较。
UVA 12563 这是我的题解:http://blog.csdn.net/good_night_sion_/article/details/53187701
在上面那一道DP里面,我的做法是利用一个结构体数组来储存当前的最优解,因此我联想到这一道题目应该也可以定义一个结构体数组来解决问题。
可是我错了,这道题目是不能那样解决的,也就是说,你定义一个二元组,以安全系数为第一优先级,资金为第二优先级来递推的话,是得不到正确答案的。
为什么?
关键在于第一优先级的单调性。
在12563里面,我们可以发现,唱的歌曲只能是增加,不会是减少的。因此这样那样定义没有问题。
但是在这里,第一优先级——安全系数并不是单调的。
比方说你前i个人看守前j个仓库的安全系数可能达到k,但是你m个人看守n个仓库的安全系数就不一定是k了,有可能小于k,但是在之前我们只会保留安全系数最大的解,这就导致有状态会丢失。
给一组数据
3 3
9 5 4
这样的话,定义结构体的做法是得不到正确答案的。
这就是我想说的,定义结构体的做法不能得到正确答案。
我在网上看到的题解,发现有两种,第一种是二分安全系数k,求出最大的安全系数,然后再DP找最小的值。第二种是k和最小的钱都用DP找。
我这里采用的是两种都是DP的做法。
我觉得状态的定义还是很好想到的,定义dp[i][j]表示前i个人看守前j个仓库的最大安全系数。
然后dp[i][j]的转移就是 min(dp[i-1][j],dp[i-1][k]+security[i]/(j-k)0<=k<j。通过这个求出最大的安全系数K
我们继续求最少的钱,dp[i][j]表当前的安全系数大于等于K的时候前i个人看守前j个仓库的最小花费
同样枚举k,dp[i][j]=min(dp[i-1][j],dp[i-1][k]+security[i])0<=k<j&&security[i]/(j-k)>=K,因为dp[i][j]的安全系数已经大于等于K了,只要保证secur[i]/(j-k)大于等于K就好。
然后仔细琢磨一下边界条件就好,我觉得已经到这一步了,边界条件已经不是很难的事情了。
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m,arr[105],dp[35][105],l,y;
int main(){
while(scanf("%d%d",&n,&m)&&n){
for(int i=1;i<=m;++i)
scanf("%d",&arr[i]);
for(int i=1;i<=n;++i)
dp[0][i]=0;
for(int i=0;i<=m;++i)
dp[i][0]=0x3f3f3f3f;
for(int i=1;i<=m;++i)
for(int j=1;j<=n;++j){
dp[i][j]=dp[i-1][j];
for(int k=0;k<j;++k)
dp[i][j]=max(dp[i][j],min(dp[i-1][k],arr[i]/(j-k)));
}
if(dp[m][n]==0){
printf("0 0\n");
continue;
}
l=dp[m][n];
for(int i=1;i<=n;++i)
dp[0][i]=0x3f3f3f3f;
for(int i=0;i<=m;++i)
dp[i][0]=0;
for(int i=1;i<=m;++i)
for(int j=1;j<=n;++j){
dp[i][j]=dp[i-1][j];
for(int k=0;k<j;++k)
if(arr[i]/(j-k)>=l)
dp[i][j]=min(dp[i][j],dp[i-1][k]+arr[i]);
}
printf("%d %d\n",l,dp[m][n]);
}
return 0;
}