该题乍看好像背包问题,但是实际上实现起来就会发现细节上还是很不同的, 这小小的不同就可能导致完全错误,所以有必要对具体的推理过程进行归纳总结,以期找到动态规划的通用思路 。
首先,我们应该先完全明确状态方程表示的含义 。 对于该题,设d[i][j]表示i个守卫,看守j个仓库的最小安全系数的最大值 。其实说的简单一点,它就表示最小安全系数 。
一定要明确这一点,才能写出正确的递推关系 。 对于一个状态d[i][j] ,要怎么转移呢? 与背包类似,前i个守卫,这是一个天然的序,所以状态肯定要依赖于前i - 1 个守卫,另外,该题还有一点:使所有仓库的最小安全系数最大 。 那么首先,选不选第i个守卫? 所以d[i][j]的初始值是d[i-1][j],表示不选该守卫,然后假设选,选几个? 所以还要枚举k (1<=k<=j) 。然后就是状态转移了,先要求最小安全系数 , 由于该守卫看守了k个仓库,所以状态由d[i-1][j-k]来,还记得前面说的吗?
它表示的就是之前最小安全系数,所以现在的最小安全系数是 min(d[i-1][j-k],p[i]/k) 。 然后对于这个结果,d[i][j]取还是不取呢?因为要求尽量大, 所以不难写出 d[i][j] = max(d[i][j],min(d[i-1][j-k],p[i]/k)); 接下来是边界处理了。 通过观察状态转移方程,易知:d[i-1][0] = INF; 而其他值则设为0,。 为什么? 因为要求尽量大啊~
另外,该题还要求在此前提下守卫的最小总能力值 。 一开始我想在第一次DP的时候顺便求出来,后来发现这是不可行的,因为求这个值需要用到第一个答案,也就是说第一个答案没有求出来,是无法求第二个的 。 状态转移方程很简单f[i][j] = min(f[i][j],f[i-1][j-k]+p[i]); 然后就是注意好边界,这里不再赘述 。
细节参见代码:
#include<bits/stdc++.h> using namespace std; const int INF = 1000000000; const int max_m = 35; const int max_n = 105; int n,m,p[max_m],d[max_m][max_n],f[max_m][max_n]; int main() { while(~scanf("%d%d",&n,&m)) { if(!n && !m) return 0; memset(d,0,sizeof(d)); for(int i=0;i<=m;i++) for(int j=0;j<=n;j++) f[i][j] = INF; for(int i=1;i<=m;i++) scanf("%d",&p[i]); for(int i=1;i<=m;i++) { d[i-1][0] = INF; for(int j=1;j<=n;j++) { d[i][j] = d[i-1][j]; for(int k=1;k<=j;k++) { d[i][j] = max(d[i][j],min(d[i-1][j-k],p[i]/k)); } } } for(int i=1;i<=m;i++) { f[i-1][0] = 0; for(int j=1;j<=n;j++) { f[i][j] = f[i-1][j]; for(int k=1;k<=j;k++) { if(p[i]/k >= d[m][n]) { f[i][j] = min(f[i][j],f[i-1][j-k]+p[i]); } } } } printf("%d %d\n",d[m][n],(d[m][n] == 0 ? 0 : f[m][n])); } return 0; }
版权声明:本文为博主原创文章,未经博主允许不得转载。