D14555. 雇拥计划
时间限制:1.0s 内存限制:256.0MB
输入文件名:test.in 输出文件名:test.out
问题描述
一位管理项目的经理想要确定每个月需要的工人,他当然知道每月所需的最少工人数。当他雇佣或解雇一个工人时,会有一些额外支出。一旦一个工人被雇佣,即使他不工作,他也将得到工资。这位经理知道雇佣一个工人的费用,解雇一个工人的费用和一个工人的工资。现他在考虑一个问题:为了把项目的费用控制在最低,他将每月雇佣多少个工人。
输入格式
共三行。第一行为月数n(不超过12)。
第二行含雇佣一个工人的费用,一个工人的工资和解雇一个工人的费用(<=100)。
第三行含n个数,分别表示每月需要的工人数(<=1000)。每个数据之间有一个空格隔开
输出格式
仅一行,表示项目的最小总费用。
样例输入
3
4 5 6
10 9 11
样例输出
199
思路:
先手暴力裸深搜,本来想找规律,结果苟到了60分,心想不会优化一下就过了吧蛤蛤蛤结果记忆化后果然过了。不过当时犯了一个小错误,dp数组开一维记录k月求得的最小值,但是状态中应当含有两个量月份和现有人数,略加修改。
Code:
记忆化搜索:
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x5f5f5f5f;
int n;
int h, p, f, ans = INF;
int man[20], dp[20][2010];
void dfs(int k, int cos, int tot) {
if (dp[k][tot] < cos) return;
dp[k][tot] = cos;
//if (cos > ans) return;
if (k > n){
//cout << cos << endl;
if(cos < ans) ans = cos;
return;
}
if (man[k] < tot){
int gundan = tot - man[k];
for (int i = 0; i <= gundan; i++) {
int w = cos + (tot - i) * p + i * f;
dfs(k + 1, w, tot - i);
}
}
if (man[k] >= tot){
int ps = man[k] - tot;
dfs(k + 1, cos + ps * h + man[k] * p, man[k]);
}
}
int main(){
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
memset(dp, INF, sizeof(dp));
cin >> n;
cin >> h >> p >> f;
for (int i = 1; i <= n; i++)
cin >> man[i];
dfs(1, 0, 0);
cout << ans << endl;
return 0;
}
然后思考贪心的方法。
考虑一个人我们在明显需要他的情况下是不会让他流离失所的,所以不能乱解雇人。
当现在工人的数量低于我们的需求时显然要招募工人,好处理EASY。
问题在于工人数量溢出需求时的解雇需求。
一个工人如果我们把他赶走之后叫回来的条件,是又缺工人的时候。如果我们不缺工人就要考虑让他滚蛋,但此时有存在把他叫回来的可能,因此应当分别计算“抛弃他再拯救他"和"包养他"的花费取最小值即可。
我们暂且只需要考虑解雇到重雇这段时间内的两项花费即可。所以保养的时间是当前时间到之后第一个比他大的时间。
注意我们要枚举一次解雇的人数。
算法复杂度似乎是O(n^2)
Code:
贪心
#include<bits/stdc++.h>
using namespace std;
int n, ans, tot = 0;
int man[20];
int hi, em, fi;
void readp(){
cin >> n;
cin >> hi >> em >> fi;
for (int i = 1; i <= n; i++)
cin >> man[i];
}
void work(){
ans = 0;
tot = 0;
for (int i = 1; i <= n; i++) {
ans += man[i] * em;//良心老板先发工资。
if (man[i] >= tot) {
int com = man[i] - tot;
ans += com * hi;
tot = man[i];
continue;
}//缺人直接叫人
int hop = tot - man[i];
for (int j = 1; j <= hop; j++) {//枚举解雇人的数量。
int we = n + 1;//注意这里的初值不能乱赋,因为可能最大值就是当前点,从而导致保养时间计算出错(这是题解上看到的)
int extra = fi;
int charity = 0;
for (int k = i + 1; k <= n; k++)
if (man[k] >= man[i] + j) we = min(we, k);
if (we <= n) extra += hi;
charity = em * (we - i);
if (charity < extra) ans += em;
else ans += fi, tot -= 1;
}//然后分别计算价格然后比较即可
}
cout << ans << endl;
}
int main() {
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
readp();
work();
return 0;
}