10163 - Storage Keepers(DP)

该题乍看好像背包问题,但是实际上实现起来就会发现细节上还是很不同的,  这小小的不同就可能导致完全错误,所以有必要对具体的推理过程进行归纳总结,以期找到动态规划的通用思路 。

首先,我们应该先完全明确状态方程表示的含义 。 对于该题,设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;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-29 11:09:02

10163 - Storage Keepers(DP)的相关文章

uva 10163 Storage Keepers (DP)

uva 10163 Storage Keepers 题目大意:有N个仓库,M个管理员,M个管理员每个人的工资都不一样,工资与他们的能力值(P)相同.一个管理员可以看管多个(n)仓库,但是仓库的安全值就会变为P / n.现在要是的最小的安全值最大,并且还要求出该状况下的最小花费. 解题思路:两次DP,第一次dp求出最大的最小安全值ans,第二次dp根据第一次dp求出的ans求出最小的花费. #include <cstdio> #include <cstring> #include &

UVA 10163-Storage Keepers(DP)

题目大意:有N(1<=N<=100)个仓库需要看管,有M(1<=M<=30)名应聘者,每个人有能力属性Pi(1<=Pi<=1000).所有仓库都是一样的,每个仓库只能被一人看守,一人可看守多个仓库,当一人看守u个仓库时,每个仓库的安全度为Uj=Pi/u,总安全度为min Uj.雇佣一个能力值为Pi的人需要花费Pi元.求最大的总安全度,和在这样的情况下的最小花费. 先dp一次,求出可能的最大总安全度max,再次dp,求出在安全度为max下的最小花费. 第一次dp:用d[i

DP(两次) UVA 10163 Storage Keepers

题目传送门 1 /* 2 题意:(我懒得写,照搬网上的)有n个仓库,m个人看管.一个仓库只能由一个人来看管,一个人可以看管多个仓库. 3 每个人有一个能力值pi,如果他看管k个仓库,那么所看管的每个仓库的安全值为 pi/k(向下取整) 4 如果某个仓库没有人看管,那么它的安全值为0.所有仓库的安全值L = min{ 每个仓库的安全值 } 5 从m个人中选择一些人雇佣,问所有仓库的安全值最高是多少,在安全值最高的情况下,求雇佣(能力值)的最少价钱. 6 DP(两次):dp[i][j]表示前i个人管

hdu 5623 KK&#39;s Number(dp)

问题描述 我们可爱的KK有一个有趣的数学游戏:这个游戏需要两个人,有N\left(1\leq N\leq 5*{10}^{4} \right)N(1≤N≤5∗10?4??)个数,每次KK都会先拿数.每次可以拿任意多个数,直到NN个数被拿完.每次获得的得分为取的数中的最小值,KK和对手的策略都是尽可能使得自己的得分减去对手的得分更大.在这样的情况下,最终KK的得分减去对手的得分会是多少? 输入描述 第一行一个数T\left( 1\leq T\leq 10\right)T(1≤T≤10),表示数据组

Ural 1353 Milliard Vasya&#39;s Function(DP)

题目地址:Ural 1353 定义dp[i][j],表示当前位数为i位时,各位数和为j的个数. 对于第i位数来说,总可以看成在前i-1位后面加上一个0~9,所以状态转移方程就很容易出来了: dp[i][j]=dp[i][j]+dp[i][j-1]+dp[i][j-2]+.......+dp[i][j-9]: 最后统计即可. 代码如下: #include <iostream> #include <cstdio> #include <string> #include <

HDU 4908 (杭电 BC #3 1002题)BestCoder Sequence(DP)

题目地址:HDU 4908 这个题是从m开始,分别往前DP和往后DP,如果比m大,就比前面+1,反之-1.这样的话,为0的点就可以与m这个数匹配成一个子串,然后左边和右边的相反数的也可以互相匹配成一个子串,然后互相的乘积最后再加上就行了.因为加入最终两边的互相匹配了,那就说明左右两边一定是偶数个,加上m就一定是奇数个,这奇数个的问题就不用担心了. 代码如下: #include <iostream> #include <stdio.h> #include <string.h&g

Sicily 1146:Lenny&#39;s Lucky Lotto(dp)

题意:给出N,M,问有多少个长度为N的整数序列,满足所有数都在[1,M]内,并且每一个数至少是前一个数的两倍.例如给出N=4, M=10, 则有4个长度为4的整数序列满足条件: [1, 2, 4, 8], [1, 2, 4, 9], [1, 2, 4, 10], [1, 2, 5, 10] 分析:可用动态规划解题,假设dp[i][j],代表满足以整数i为尾数,长度为j的序列的个数(其中每一个数至少是前一个数的两倍).那么对于整数i,dp[i][j] 等于所有dp[k][j-1]的和,其中k满足:

UVA542 - France &#39;98(dp)

UVA542 - France '98(dp) 题目链接 题目大意:之前题目意思还以为看懂了,其实没看明白,它已经把各个选手分在各自所在的区域里面,这就意味着第一次的PK的分组已经确定,而且冠军必须是从两个左右分区出来的胜利者才有机会pk冠军. 解题思路:那么从1-16这个大的区间内诞生出来的冠军可能是来自左边,也可能是右边,然后再左边右边的子区间递归找出冠军.f[i][l][r]表示l-r这个区间的胜利者是i的概率,那么假设i在区间的最左边,f[i][l][r] = Sum(f[i][l][m

HDU 4968 Improving the GPA(dp)

HDU 4968 Improving the GPA 题目链接 dp,最大最小分别dp一次,dp[i][j]表示第i个人,还有j分的情况,分数可以减掉60最为状态 代码: #include <cstdio> #include <cstring> #include <algorithm> using namespace std; int t, avg, n; double dp1[15][405], dp2[15][405]; double get(int x) { if