题目大意
一家公司完成一个项目,需要n个月的时间,每个月至少雇佣a1…an个工人,解雇工人和雇佣工人都需要一定的费用,每个月还要给雇来的工人工资,问你最少的花费是多少(不用输出方案,只有输出最优解)
解题思路1
将每个月的操作封装为(雇佣工人、支付薪水、解雇工人),所以每月的花费就是这三部分的总和。
- 雇佣工人
这部分取决于上一个月雇佣工人数量是否大于等于这个月所需工人数,如果不够那么雇佣工人直到恰好满足当月需求(雇佣多于本月数量的工人显然是没有效益的)。 - 支付薪水
这部分也很简单,就是用现在雇佣的人数乘以薪水,即temp_cost += now_hire * salary
- 解雇工人
这部分是难点,因为解雇多少个工人是取决于之后月份所需要的工人数。假设当前是第i个月,如果在第j个月需要n个工人(j > i) , 并且在i + 1到j之间的月份所需要的工人数都小于n。那么如果在第i月解雇这n个工人导致的花费是在i月的解雇费与在j月的雇佣费用(n * (cost_hire + cost_fire)
),如果第i月没有解雇这n个工人,那么导致的花费就是中间j - i - 1个月的薪水(n * (j - i - 1) * salary
)。也就是说当n * (cost_hire + cost_fire) <= n * (j - i - 1)*salary
时,这n个人应该被解雇,否则要继续雇佣。
这只是一个大致判断,还存在一些细节问题,如当月人数为之后月份所需人数严格最大值时的处理,等等。
解体思路1——AC代码
#include <iostream>
#include <stdio.h>
#include <math.h>
#define MaxNum 20
using namespace std
int N;
int a[MaxNum];
void solve(int case_n)
{
int i , j , k;
int s = 0;
int tmax , temp;
int A , B;
int tbegin = 1 , f = 1;
int cost_hire, salary , cost_fire;
int temp_cost , to_fire , total_cost = 0 , now_hire;
int extra_worker_cost , extra_worker_num;
cin >> cost_hire >> salary >> cost_fire;
for( i = 1 ; i <= N ; i ++)
cin >> a [i];
now_hire = 0;
for( i = 1 ; i <= N ; i ++)
{
temp_cost = 0; //当月花费计数器归零
if( now_hire < a[i] ) //如果现在雇佣的工人不够,则必须追加雇佣
{
temp_cost += cost_hire * ( a[i] - now_hire); //雇佣工人的花费
now_hire = a[i];
}
temp_cost += now_hire * salary; //当月雇佣工人的薪水
if( i == N ) //如果是最后一个月不需要判断是否解雇工人
{
total_cost += temp_cost;
break;
}
//判断当月解雇工人的数量与花费
tmax = 0; to_fire = 0; f = 1; //初始化
for( j = i + 1 ; j <= N && f ; j ++)
{
temp = a[j];
if( temp > now_hire ) //j月雇佣工人数大于当前雇佣人数
{
temp = now_hire;
f = 0;
}
if( temp > tmax )
{
extra_worker_cost = (j - i - 1) * salary - (cost_hire + cost_fire); //不fire相比fire的开销
extra_worker_num = temp - tmax;
tmax = temp;
if( extra_worker_cost > 0) to_fire += extra_worker_num; //当fire更省钱时,to_fire累加
}
}
if( f == 1 ) //当月雇佣工人数为之后的严格最大值
{
extra_worker_cost = (N - i) * salary - cost_fire;
extra_worker_num = now_hire - tmax;
if( extra_worker_cost > 0) to_fire += extra_worker_num;
}
temp_cost += to_fire * cost_fire;
now_hire -= to_fire;
total_cost += temp_cost;
}
cout << total_cost << endl;
}
int main(int argc, char** argv) {
int i = 1;
while(scanf("%d",&N))
{
if( N == 0) break;
else
solve( i );
i ++;
}
return 0;
}
解题思路2——动态规划
dp[i][j]存储若第i个月雇佣j个人所花费的最小费用(包括前i-1个月的费用)。
那么对于dp[i][j](a[i] <= j <= max_worker)
,一定是由dp[i - 1][k](a[i - 1] <= k <= max_people) + abs(j - k) * price + j * salary
产生,其中当j - k为正数时,v = cost_hire,否则v = cost_fire(即第i-1雇佣k个工人所产生的最小费用,加上在第i个月雇佣或解雇工人的费用,以及第i个月的薪水)。所以状态转移方程就是dp[i][j] = max( dp[i - 1][k](a[i - 1] <= k <= max_people) + abs(j - k) * price + j * salary )
解题思路2——AC代码
#include <iostream>
#include <stdio.h>
#include <math.h>
#define MaxNum 20
#define MaxWorker 10001
using namespace std;
int N;
int a[MaxNum];
int dp[MaxNum][MaxWorker];
void solve(int case_n)
{
int i , j , k;
int tmin , temp;
int cost_hire, salary , cost_fire , now_hire;
int max_worker , min_worker;
int temp_cost , price , min_cost = 0;
cin >> cost_hire >> salary >> cost_fire;
for( i = 1 ; i <= N ; i ++)
{
cin >> a [i];
if( i == 1 ) max_worker = min_worker = 1;
max_worker = max(max_worker , a[i]);
min_worker = min(min_worker , a[i]);
}
for( i = a[1] ; i <= max_worker ; i ++) //dp初始化
dp[1][i] = (cost_hire + salary) * i;
for( i = 2 ; i <= N ; i ++)
{
for( j = a[i] ; j <= max_worker ; j ++)
{
for( k = a[i - 1] ; k <= max_worker ; k ++)
{
if( k > j ) price = cost_fire;
else price = cost_hire;
temp = dp[i - 1][k] + abs(k - j) * price + j * salary;
if( k == a[i - 1] ) tmin = temp;
tmin = min(tmin , temp);
}
dp[i][j] = tmin;
}
}
min_cost = dp[N][a[N]];
for( i = a[N] ; i <= max_worker ; i ++)
min_cost = min(min_cost , dp[N][i]);
cout << min_cost << endl;
}
int main(int argc, char** argv) {
int i = 1;
while(scanf("%d",&N))
{
if( N == 0) break;
else
solve( i );
i ++;
}
return 0;
}