洛谷题单指南-暴力枚举-P2392 kkksc03考前临时抱佛脚
原题链接:https://www.luogu.com.cn/problem/P2392
题意解读:由于可以同时计算两道同一科的题目,只需要把某一科题目分两堆,使得两堆总时长之差最小,时长较大的一堆就是完成这一科的最短时间。
解题思路:
既然知道了要把一科题目分两堆,关键是如何分堆呢?
比较容易犯的错是用贪心来解题:把一科题目按时长排序,从小到大取出放入一堆,剩余的规到另一堆,计算两堆之差最小的点。
贪心法的问题很容易举出反例,比如某一科题目时长为:1、3、9、11,如果按此方法,得到最短时长为13,而实际是1+11 -(3+9)之差最小,最短时长应该是12。
既然贪心不好使,就直接使用枚举子集的方式,对于某一科s个题目,每一道题目选或者不选,来划分成两组,计算两组和的差值,取差值最小的一组和作为最短时长。
前面讲过,枚举子集可以采用二进制法,也可以使用DFS,这里采用二进制法。DFS的代码参考:https://www.cnblogs.com/jcwy/p/18052141
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 25;
int s1, s2, s3, s4;
int a[N], b[N], c[N], d[N];
//枚举所有的子集,计算子集和与剩余元素和之差的绝对值最小时,较大的那一组元素和
int gettime(int e[N], int len)
{
int res = 0, minx = 2e9;
for(int i = 0; i < pow(2, len); i++) // 对于长度为len的数组,从中选数的方案是0 ~ 2^len - 1对应的二进制,1表示选,0表示不选
{
int sum1 = 0, sum2; //sum1表示选择的元素之和,sum2表示剩余元素之和
for(int j = 0; j < len; j++) //遍历每一个二进制位
{
if(i >> j & 1) //如果第j+1个二进制位是1
{
sum1 += e[j + 1]; //选择第j+1个数
}
}
sum2 = e[0] - sum1; //计算剩余元素之和
if(abs(sum1 - sum2) < minx)
{
minx = abs(sum1 - sum2); //记录选择元素和与剩余元素和之差绝对值的最小值
res = max(sum1, sum2); //当两组和相差最小时,较大的数即为答案
}
}
return res;
}
int main()
{
cin >> s1 >> s2 >> s3 >> s4;
for(int i = 1; i <= s1; i++)
{
cin >> a[i];
a[0] += a[i]; //a[0]是元素之和
}
for(int i = 1; i <= s2; i++)
{
cin >> b[i];
b[0] += b[i]; //b[0]是元素之和
}
for(int i = 1; i <= s3; i++)
{
cin >> c[i];
c[0] += c[i]; //c[0]是元素之和
}
for(int i = 1; i <= s4; i++)
{
cin >> d[i];
d[0] += d[i]; //d[0]是元素之和
}
cout << gettime(a, s1) + gettime(b, s2) + gettime(c, s3) + gettime(d, s4);
return 0;
}