题意:已知雇佣员工花费(h)、解雇员工花费(f)、员工每月薪水(s),员工未被解雇的话即使未工作也要付薪水,现知道每个月需要几名员工,求最低花费。
很显然,刷 DP 专题的我早早地就意识到这是一道 DP 题(呵呵,废话你在刷DP```),我最开始的思路是这样的,开一个一维数组 dp [ i ] 来记录第 i 个月的最低花费情况,同时另开一个平行数组记录该情况下的人数,通过第 i 月的需求人数与上个月的最优情况的人数比较,进行 DP 。但是很快我就发现,情况的种类很多很复杂,并且在第 i 个月的需求人数超过上个月人数时,我必须考虑到是否可能在更前面就不解雇员工维持到第 i 月,很明显我的想法并不可行。紧接着,我就在往之前学过的 DP 算法考虑,很显然,状压 DP 是考虑有多个互不相关的事物需要考察是否在某个状态时使用,与本题不符,而记忆化搜索又需要又一些固定步骤来进行 dfs ,也不是这题的情况。纠结了很久之后,我还是无奈地看了题解```
果然是智商上的压制啊!这题并没有涉及新的什么算法,没有超出我已学的东西,所以本应该是我可以推出来的,可是我却没有想到这样的做法,果然还是有很长的路要走啊。
解法是这样的,在读取每月需求人数 a [ i ] 的时候就记录下需求人数的最大值 b ,也就是整个过程中最多需要 b 人,开一个二维数组 dp , dp [ i ] [ j ] 表示第 i 月雇佣了 j 人的最小花费金额,由于每个月至少要有该月需求人数 a [ i ] 名员工,所以 j 的范围就是从 a [ i ] 到 b , i = 1 时直接初始化 dp [ 1 ] [ j ] ,都等于 j * ( h + s ),从第二个月开始,每个月的每种人数情况就是从上个月的所有人数情况转变来的最小值:
dp [ i ] [ j ] = min ( dp [ i ] [ j ] , dp [ i - 1 ] [ k ] + mon ( k , j ) + j * s);a [ i - 1 ] <= k <= b;
即当(第 i - 1 月有 k 个人时最小花费 + 从 k 人变为 j 人时的雇佣/解雇花费 + 第 i 月时 j 名员工的总工资)比 dp [ i ] [ j ] 小的时候就用这个值更新 dp [ i ] [ j ];
1 #include<stdio.h>
2 #include<string.h>
3 #define min(a,b) a<b?a:b
4 #define max(a,b) a>b?a:b
5 #define inf 0x3f3f3f3f
6 int a[13],dp[13][100000],h,s,f;
7
8 int mon(int last,int now){
9 if(now<last)return (last-now)*f;
10 if(now>last)return (now-last)*h;
11 return 0;
12 }
13
14 int main(){
15 int T;
16 while(scanf("%d",&T)!=EOF&&T!=0){
17 int i,j,k,l=inf,b=0;
18 scanf("%d%d%d",&h,&s,&f);
19 for(i=1;i<=T;i++){
20 scanf("%d",&a[i]);
21 if(a[i]>b)b=a[i];
22 if(a[i]<l)l=a[i];
23 }
24 // printf("T=%d h=%d %d %d ",T,h,s,f);
25 for(j=a[1];j<=b;j++){
26 dp[1][j]=j*h+j*s;
27 // printf("%d ",dp[1][j]);
28 }
29 for(i=2;i<=T;i++){
30 for(j=a[i];j<=b;j++){
31 dp[i][j]=inf;
32 for(k=a[i-1];k<=b;k++){
33 dp[i][j]=min(dp[i][j],dp[i-1][k]+mon(k,j)+j*s);
34 }
35 }
36 }
37 l=inf;
38 for(i=a[T];i<=b;i++)if(dp[T][i]<l)l=dp[T][i];
39 printf("%d\n",l);
40 }
41 return 0;
42 }