题意:
有四门学科,每个学科最多二十个任务,每次最多同时进行一门学科的两个任务,给出各学科各任务的时间,求做完全部任务所需最小的时间
样例解释:
输入
1 2 1 3
5
4 3
6
2 4 3
输出
20
第一门学科用时5分钟,第二门学科两门同时做,总共用时4分钟,第三门学科用时6分钟,第四门学科,做(2 + 3) 和 4, 即做2的同时做4, 做完2后一边继续做3, 一边继续做4,总共用时5分钟,总共用时 5 + 4 + 6 + 5 = 20 分钟
分析:
如何让总用时最小?
由于只能一门一门学科做,当每一门学科都用时最小的时候,总用时也最小。
如何让每一门学科用时最小?
每次最多同时做两个任务,可以将全部任务划分为两个集合,让两个集合的时间之和尽可能接近(靠近n / 2), 此时这一门学科用时最小
即通过子集枚举,
找到较小(<= n / 2)且最大的一个子集
。当前学科所需要的最少时间,即较大(>= n / 2)的子集的时间和, 也即当前学科所需总时间 - 较小子集的总时间
分别对每一门学科进行子集枚举(使用DFS), 在[1, num]区间(num为当前学科任务的个数)遍历,每个元素都有两种情况,选 or 不选。
选: 如果选择了当前的任务总时间仍然不超过 n / 2(较小的子集), 就将当前任务的时间加入集合并递归下一层。在搜索结束的时候与当前最大的子集和比较,若当前方案更优则更新最大的子集和(较小的子集和最大)
不选:如果不选,直接递归下一层即可
参考代码
#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int s[5]; //存4个学科的任务个数
int t[30]; //存每个学科各个任务的时间
int maxtime; //当前学科 总时间之和 <= n / 2 并且 尽可能大的 集合的总时间(即较小集合中时间总和)
int nowtime; //暂存 较小的集合的时间总和的 当前方案
int num;//当前学科的任务数量
int cnt;//当前学科的总时间
void dfs(int x) //dfs做的是子集枚举, x是递归层数, 枚举区间为[1, sum]
{
//边界条件: 当前递归的层数超过了当前学科的任务数量num
if(x > num)
{
maxtime = max(maxtime, nowtime); //如果当前子集的总时间nowtime更大则更新maxtime
return;
}
//每一个元素有两种选择,选和不选
//选
if(t[x] + nowtime <= cnt / 2) //选择的条件: 小于总时间的一半
{
nowtime += t[x];
dfs(x + 1); //递归下一层
nowtime -= t[x]; //恢复现场
}
//不选
dfs(x + 1);
}
int main()
{
IOS
cin >> s[1] >> s[2] >> s[3] >> s[4]; //四个学科各自的任务数
int ans = 0; //记录全部学科的最终最少总用时
for(int i = 1; i <= 4; i++) //分别枚举四个学科的时间情况
{
//每一个学科需要初始化的变量
nowtime = 0;
cnt = 0; //每一个学科的任务总用时
num = s[i]; //num表示的是当前学科有几项任务, 即s[i]
maxtime = 0; //maxtime 记录的是当前学科较小的那一个子集总时间和, 用dfs去做子集枚举
for(int j = 1; j <= s[i]; j++) //每个学科有s[i]个任务
{
cin >> t[j];
cnt += t[j]; //cnt表示当前科目总共需要花的时间
}
dfs(1); //对当前学科通过深搜进行子集枚举,目的是将其全部任务分成两个子集, 让他们尽可能接近(枚举一个使其<= n / 2 且尽可能大),dfs后maxtime记录了两子集中较小的子集的总时间,用总时间 - maxtime就是当前任务最少的所需要花的时间(两个集合中的max)
ans += cnt - maxtime; //当前学科的最小用时,累加起来即为总的最小用时
}
cout << ans << endl;
return 0;
}