背包DP:Make_Full_Use_Of模型:kkksc03考前临时抱佛脚

kkksc03考前临时抱佛脚

题目背景

kkksc03 的大学生活非常的颓废,平时根本不学习。但是,临近期末考试,他必须要开始抱佛脚,以求不挂科。

题目描述

这次期末考试,kkksc03 需要考 44 科。因此要开始刷习题集,每科都有一个习题集,分别有 s1,s2,s3,s4s_1,s_2,s_3,s_4 道题目,完成每道题目需要一些时间,可能不等(A1,A2,,As1A_1,A_2,\ldots,A_{s_1}B1,B2,,Bs2B_1,B_2,\ldots,B_{s_2}C1,C2,,Cs3C_1,C_2,\ldots,C_{s_3}D1,D2,,Ds4D_1,D_2,\ldots,D_{s_4})。

kkksc03 有一个能力,他的左右两个大脑可以同时计算 22 道不同的题目,但是仅限于同一科。因此,kkksc03 必须一科一科的复习。

由于 kkksc03 还急着去处理洛谷的 bug,因此他希望尽快把事情做完,所以他希望知道能够完成复习的最短时间。

输入格式

本题包含 55 行数据:第 11 行,为四个正整数 s1,s2,s3,s4s_1,s_2,s_3,s_4

22 行,为 A1,A2,,As1A_1,A_2,\ldots,A_{s_1}s1s_1 个数,表示第一科习题集每道题目所消耗的时间。

33 行,为 B1,B2,,Bs2B_1,B_2,\ldots,B_{s_2}s2s_2 个数。

44 行,为 C1,C2,,Cs3C_1,C_2,\ldots,C_{s_3}s3s_3 个数。

55 行,为 D1,D2,,Ds4D_1,D_2,\ldots,D_{s_4}s4s_4 个数,意思均同上。

输出格式

输出一行,为复习完毕最短时间。

样例 #1

样例输入 #1

1
2
3
4
5
1 2 1 3        
5
4 3
6
2 4 3

样例输出 #1

1
20

提示

1s1,s2,s3,s4201\leq s_1,s_2,s_3,s_4\leq 20

1A1,A2,,As1,B1,B2,,Bs2,C1,C2,,Cs3,D1,D2,,Ds4601\leq A_1,A_2,\ldots,A_{s_1},B_1,B_2,\ldots,B_{s_2},C_1,C_2,\ldots,C_{s_3},D_1,D_2,\ldots,D_{s_4}\leq60

本题为背包dp的 “ 简单 ” 变式:虽然简单变一变本蒟蒻已经不会了。

一开始的思路是贪心算法:对每一组而言先排序,然后从后向前把元素往两个空脑子里丢,哪个脑子目前内容比较少就丢哪个。

代码如下,喜提零分(听取蛙声一片)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
using namespace std;
#include <algorithm>
#include <queue>
const int maxsize = 25;

int n, inp;
int min_time(int a[],int n)
{
if(n==1)return a[0];
int lsum=0,rsum=0;
sort(a,a+n);
for(int i=n-1;i>=0;i--)
{
if(lsum>rsum)rsum+=a[i];
else lsum+=a[i];
}
return max(lsum,rsum);
}
int main()
{
int sum_time=0,na,nb,nc,nd;
int a[maxsize],b[maxsize],c[maxsize],d[maxsize];
cin>>na>>nb>>nc>>nd;

for(int i=0;i<na;i++)
cin>>a[i];
for(int i=0;i<nb;i++)
cin>>b[i];
for (int i = 0; i < nc; i++)
cin >> c[i];
for (int i = 0; i < nd; i++)
cin >> d[i];

cout << min_time(a, na) + min_time(b, nb) + min_time(c, nc) + min_time(d, nd) << endl;

return 0;
}

注意:以上代码为错误代码!!!!!!!!!

因为可以找到一组hack数据

1
2
//在某一行:
10 10 6 6 5 3

按照贪心算法

10 6 5

10 6 3

max为21

但是实际上:

10 10

6 6 5 3

max为20

于是验证了贪心的不可行性,而这种退一步达到全局最优解的情况,就应该使用dp

这道题使用的是最基本的背包dp。我们由以上思路可以知晓,当两个脑子处理的内容大小最接近 的时候将会达到本组最优解。于是我给第一个脑子一个容量预算为sum/2(即背包容量),在运算过程中不能超过这个预算。通过将每个选择装进背包的物品的v等价于其重量w这种方法,由此得出最大化利用sum/2的背包空间所能装得下的处理总量,即dp[ sum/2 ];

而max(sum-dp[ sum/2 ],dp[ sum/2 ])即为某一组耗时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int dynamic_programming(int sum,int homework[],int n)
{
if(n==1)return homework[1];
int dp[2501]={0};
for(int i=1;i<=n;i++)
{
for(int j=sum/2;j>=homework[i];j--)
{
dp[j]=max(dp[j],dp[j-homework[i]]+homework[i]);
}
}
return max(sum - dp[sum / 2], dp[sum / 2]);
    // return sum - dp[sum / 2];
}

此外由于int是除以2(在正数范围内)向下取整的特性,可以数学证明sum-dp[ sum/2 ]总是大于dp[ sum/2 ],所以注释行也还可以那样输出。

最后,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <bits/stdc++.h>
using namespace std;
int a[5];
int dynamic_programming(int sum,int homework[],int n)
{
if(n==1)return homework[1];
int dp[2501]={0};
for(int i=1;i<=n;i++)
{
for(int j=sum/2;j>=homework[i];j--)
{
dp[j]=max(dp[j],dp[j-homework[i]]+homework[i]);
}
}
return max(sum - dp[sum / 2], dp[sum / 2]); // return sum - dp[sum / 2];
}
int main()
{
int b[21] = {0},ans=0;
for (int i = 1; i <= 4; i++)
cin >> a[i];
for(int i=1;i<=4;i++)
{
int sum=0;
memset(b,0,sizeof(b));
for(int j=1;j<=a[i];j++)
{
cin>>b[j];
sum+=b[j];
}
ans+=dynamic_programming(sum,b,a[i]);
}

cout<<ans<<endl;
return 0;
}