kkksc03考前临时抱佛脚
原题
题目背景
kkksc03 的大学生活非常的颓废,平时根本不学习。但是,临近期末考试,他必须要开始抱佛脚,以求不挂科。
题目描述
这次期末考试,kkksc03 需要考 4 4 4 科。因此要开始刷习题集,每科都有一个习题集,分别有 s 1 , s 2 , s 3 , s 4 s_1,s_2,s_3,s_4 s1,s2,s3,s4 道题目,完成每道题目需要一些时间,可能不等( A 1 , A 2 , … , A s 1 A_1,A_2,\ldots,A_{s_1} A1,A2,…,As1, B 1 , B 2 , … , B s 2 B_1,B_2,\ldots,B_{s_2} B1,B2,…,Bs2, C 1 , C 2 , … , C s 3 C_1,C_2,\ldots,C_{s_3} C1,C2,…,Cs3, D 1 , D 2 , … , D s 4 D_1,D_2,\ldots,D_{s_4} D1,D2,…,Ds4)。
kkksc03 有一个能力,他的左右两个大脑可以同时计算 2 2 2 道不同的题目,但是仅限于同一科。因此,kkksc03 必须一科一科的复习。
由于 kkksc03 还急着去处理洛谷的 bug,因此他希望尽快把事情做完,所以他希望知道能够完成复习的最短时间。
输入格式
本题包含 5 5 5 行数据:第 1 1 1 行,为四个正整数 s 1 , s 2 , s 3 , s 4 s_1,s_2,s_3,s_4 s1,s2,s3,s4。
第 2 2 2 行,为 A 1 , A 2 , … , A s 1 A_1,A_2,\ldots,A_{s_1} A1,A2,…,As1 共 s 1 s_1 s1 个数,表示第一科习题集每道题目所消耗的时间。
第 3 3 3 行,为 B 1 , B 2 , … , B s 2 B_1,B_2,\ldots,B_{s_2} B1,B2,…,Bs2 共 s 2 s_2 s2 个数。
第 4 4 4 行,为 C 1 , C 2 , … , C s 3 C_1,C_2,\ldots,C_{s_3} C1,C2,…,Cs3 共 s 3 s_3 s3 个数。
第 5 5 5 行,为 D 1 , D 2 , … , D s 4 D_1,D_2,\ldots,D_{s_4} D1,D2,…,Ds4 共 s 4 s_4 s4 个数,意思均同上。
输出格式
输出一行,为复习完毕最短时间。
样例 #1
样例输入 #1
1 2 1 3
5
4 3
6
2 4 3
样例输出 #1
20
提示
1 ≤ s 1 , s 2 , s 3 , s 4 ≤ 20 1\leq s_1,s_2,s_3,s_4\leq 20 1≤s1,s2,s3,s4≤20。
1 ≤ A 1 , A 2 , … , A s 1 , B 1 , B 2 , … , B s 2 , C 1 , C 2 , … , C s 3 , D 1 , D 2 , … , D s 4 ≤ 60 1\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 1≤A1,A2,…,As1,B1,B2,…,Bs2,C1,C2,…,Cs3,D1,D2,…,Ds4≤60。
解1
典型的01背包问题
几乎是01背包模板题,无脑刷过
不同点
要分四次记录答案
坑
记牢每次一定要初始化背包数组
#include<bits/stdc++.h>
using namespace std;
int s[5],a[21],f[1210],ans=0;
int main()
{
for(int i=1;i<=4;i++)
cin>>s[i];//四个背包
for(int k=1;k<=4;k++)//依次进行枚举
{
int sum=0;
for(int j=1;j<=s[k];j++)
{
cin>>a[j];
sum+=a[j];//记录背包大小
}
memset(f,0,sizeof(f));//清空背包
f[0]=1;//初始化
for(int i=1;i<=s[k];i++)
for(int j=sum;j>=0;j--)
if(f[j])//如果上一个大小可以取
f[j+a[i]]=1;//那么加上物品后的容量也可以取
int tmp=sum,res;
for(int i=0;i<=sum;i++)//枚举答案
if(f[i]&&tmp>=abs(i-(sum-i)))
{
tmp=abs(i-(sum-i));
res=max(i,sum-i);
}
ans+=res;//将答案记录下来
}
cout<<ans<<endl;
}
解2
P党的福利
这道题可以用dp的方法来做。其实和01背包差不多。
其实就是将每个科目的所有复习时间分成两部分,尽量使得两部分的总时间都接近一半。
背包结束后f[m/2]总是小于等于m-f[m/2],要加最大值。这就是这道题的唯一坑点吧……
没学过01背包请先看看 P2871 ,链接放这不谢。一定要学会哦~
下面上代码:
var
w:array[1..100000] of longint;
f:array[1..100000] of longint;
a,b,c,d,ans,i:longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a)
else exit(b);
end;
procedure dp(n:longint);//dp函数,要用就拿来用
var
i,j,cnt:longint;
begin
fillchar(f,sizeof(f),0);
cnt:=0;
for i:=1 to n do inc(cnt,w[i]);
for i:=1 to n do
for j:=cnt div 2 downto w[i] do
f[j]:=max(f[j],f[j-w[i]]+w[i]);//状态转移方程
inc(ans,cnt-f[cnt div 2])//(坑点)
end;
begin
readln(a,b,c,d);
ans:=0;
for i:=1 to a do read(w[i]); readln; dp(a);
for i:=1 to b do read(w[i]); readln; dp(b);
for i:=1 to c do read(w[i]); readln; dp(c);
for i:=1 to d do read(w[i]); readln; dp(d);
writeln(ans);
end.
解3
贪心题:既然是算较短的时间,如果左脑所用时间少就加在左脑,如果右脑所用时间少就加在右脑
#include<bits/stdc++.h>
using namespace std;
int a[5],i,j,sum1,sum2,t,homework;
int main(){
for(i=1;i<=4;i++)
cin>>a[i];//输入
for(i=1;i<=4;i++){
sum1=sum2=0;//两边脑子时间清零
for(j=1;j<=a[i];j++)
{cin>>homework;
if(sum1<=sum2) sum1+=homework;
else sum2+=homework;}//哪边时间短就加在哪边
t+=max(sum1,sum2);//取较长时间累加
}cout<<t;//输出
return 0;
}
满怀期待的提交后,结果有点震惊 结果
果然,贪心不是正解
后来思考了一下,便感觉是dp,对于一道题只有两个状态,一是加到左脑,二是加到右脑,所以是01背包
这里还可以用另一个思想,将一边的脑子加到最接近一半则另一边脑子时间就是正解
#include<bits/stdc++.h>
using namespace std;
int a[5],i,j,k,sum,t,homework[21],dp[2501];
int main(){
for(i=1;i<=4;i++)
cin>>a[i];
for(i=1;i<=4;i++){
sum=0;
for(j=1;j<=a[i];j++)
{cin>>homework[j];//输入
sum+=homework[j];}//总时间累加
for(j=1;j<=a[i];j++)
for(k=sum/2;k>=homework[j];k--)//只要是总和的一半
dp[k]=max(dp[k],dp[k-homework[j]]+homework[j]);//01背包
t+=sum-dp[sum/2];//累加为另一个脑子
for(j=1;j<=sum/2;j++)
dp[j]=0;//清零
}
cout<<t;//输出
return 0;
}
[HNOI2001] 产品加工
题目描述
某加工厂有 A、B 两台机器,来加工的产品可以由其中任何一台机器完成,或者两台机器共同完成。由于受到机器性能和产品特性的限制,不同的机器加工同一产品所需的时间会不同,若同时由两台机器共同进行加工,所完成任务又会不同。
某一天,加工厂接到 n n n 个产品加工的任务,每个任务的工作量不尽一样。
你的任务就是:已知每个任务在 A 机器上加工所需的时间 t 1 t_1 t1,B 机器上加工所需的时间 t 2 t_2 t2 及由两台机器共同加工所需的时间 t 3 t_3 t3,请你合理安排任务的调度顺序,使完成所有 n n n 个任务的总时间最少。
输入格式
第一行为一个整数 n n n。
接下来 n n n 行,每行三个非负整数 t 1 , t 2 , t 3 t_1,t_2,t_3 t1,t2,t3,分别表示第 i i i 个任务在 A 机器上加工、B 机器上加工、两台机器共同加工所需要的时间。如果所给的时间 t 1 t_1 t1 或 t 2 t_2 t2 为 0 0 0 表示任务不能在该台机器上加工,如果 t 3 t_3 t3 为 0 0 0 表示任务不能同时由两台机器加工。
输出格式
仅一行一个整数,表示完成所有 n n n 个任务的最少总时间。
样例 #1
样例输入 #1
5
2 1 0
0 5 0
2 4 1
0 0 3
2 1 1
样例输出 #1
9
提示
对于所有数据,有 1 ≤ n ≤ 6 × 1 0 3 1\le n\le 6\times 10^3 1≤n≤6×103, 0 ≤ t 1 , t 2 , t 3 ≤ 5 0\le t_1,t_2,t_3\le 5 0≤t1,t2,t3≤5。
解1
Code
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
for (int i = 1; i <= n; i++) {
up += max(t1[i], t3[i]);
for (int j = up; j >= 0; j--) {
int p = 0x3f3f3f3f;
if (j >= t1[i]) p = dp[j - t1[i]];
int q = dp[j] + t2[i];
int r = 0x3f3f3f3f;
if (j >= t3[i]) r = dp[j - t3[i]] + t3[i];
if (t1[i] == 0) p = 0x3f3f3f3f;
if (t2[i] == 0) q = 0x3f3f3f3f;
if (t3[i] == 0) r = 0x3f3f3f3f;
dp[j] = MIN(p, q, r);
}
}
解2
我们首先看到,这个题同时维护两个甚至是三个进程,实在是不好想。我也是第一次看到有把数组下标当作最优状态求答案的,DP题见的应该还是越多越好。
我们试着用f[i][j]来维护当前加工第i个物品,A机器用时为j时B机器的最短用时。①我们不用担心排序问题,②也不用担心同时做会耽误某个空档的时间。①因为我们把这个模型当作一个背包,只管加入工件,不管添加顺序(背包不也是这样么)。②同时做的后效性问题:因为这里做的时候不用排序,所以我们把所有加入背包的同时进行的进程提到最前面去。
我们看这样一组数据:
3
5 0 0
0 2 0
0 0 3
我们模拟这个过程,是这样的(分别编号为工件1,2,3)
因为我们用的是背包,所以等价于下面这样:(贪心的思想)
所以在没有顺序的时候,直接按背包做。我们的状态转移方程就是这样,不过状态只能单点转移而不是像背包那样只要比c[i]大都能转移:
f[][]=∞ f[0][0]=0
f[i][j]=min{f[i-1][j]+t2[i],f[i-1][j-t1[i]],f[i-1][j-t3[i]]+t3[i]}
因为这个题数据范围达到$ 5×6000^2=1.8\times 10^8$,超出1亿次,并且空间也会超128M,因此我们要优化枚举下界,并滚掉第一维。滚动比较好做,只要保存好转移t2时的状态,和背包相同。
枚举下界的调整:我们可以看出,因为状态是非严格单调递增的,所以我们如果发现对∀i∈[0,k],f[i]=∞,那么k以下的状态已经作废了,不会再被用到。此时我们的枚举下界down就可以调整到k了,并且每次做完检验是否可以继续更新。
同时要记得在输入的时候记录上界(up+=max{t1[i],t2[i],t3[i]}),并记得每次置为∞防止用到过时状态(尤其是做t2时可能会碰到两层前的状态)。
Code:(luogu开了O2才能过?)
#include<cstring>
#include<cstdio>
int f[30100];
int t[4][6666];
int max(int x,int y)//重载貌似比stl快一点
{
return x>y?x:y;
}
int min(int x,int y)
{
return x<y?x:y;
}
int main()
{
memset(f,0x3f,sizeof(f));
int n,up=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=3;j++)
scanf("%d",&t[j][i]);
up+=max(t[1][i],max(t[2][i],t[3][i]));
}
f[0]=0;
int down=0,tmp;
for(int i=1;i<=n;i++)
{
for(int j=up;j>=down;j--)
{
tmp=f[j];//初始化前存一下原来的值,但是只有t2能用(和其他题解不同)
f[j]=0x3f3f3f3f;//相当于每次初始化
if(j>=t[1][i]&&t[1][i]>0)
f[j]=f[j]<f[j-t[1][i]]?f[j]:f[j-t[1][i]];
if(j>=t[3][i]&&t[3][i]>0)
f[j]=f[j]<f[j-t[3][i]]+t[3][i]?f[j]:f[j-t[3][i]]+t[3][i];
if(t[2][i]>0)
f[j]=f[j]<tmp+t[2][i]?f[j]:tmp+t[2][i];
}
while(f[down]>=0x3f3f3f3f)
down++;
}
int ans=up;
for(int i=down;i<=up;i++)
{
tmp=max(f[i],i);
ans=ans<tmp?ans:tmp;
}
printf("%d\n",ans);
return 0;
}
任务调度
题解
另一篇题解
有若干个任务需要在一台机器上运行。
它们之间没有依赖关系,因此可以被按照任意顺序执行。
该机器有两个 CPU 和一个 GPU。
对于每个任务,你可以为它分配不同的硬件资源:
- 在单个 CPU 上运行。
- 在两个 CPU 上同时运行。
- 在单个 CPU 和 GPU 上同时运行。
- 在两个 CPU 和 GPU 上同时运行。
一个任务开始执行以后,将会独占它所用到的所有硬件资源,不得中断,直到执行结束为止。
第 i i i 个任务用单个 CPU,两个 CPU,单个 CPU 加 GPU,两个 CPU 加 GPU 运行所消耗的时间分别为 a i , b i , c i a_i,b_i,c_i ai,bi,ci 和 d i d_i di。
现在需要你计算出至少需要花多少时间可以把所有给定的任务完成。
输入格式
输入的第一行只有一个正整数 n n n,是总共需要执行的任务个数。
接下来的 n n n 行每行有四个正整数 a i , b i , c i , d i a_i, b_i, c_i, d_i ai,bi,ci,di,以空格隔开。
输出格式
输出只有一个整数,即完成给定的所有任务所需的最少时间。
数据范围
1
≤
n
≤
40
1 \le n \le 40
1≤n≤40,
1
≤
a
i
,
b
i
,
c
i
,
d
i
≤
10
1 \le a_i,b_i,c_i,d_i \le 10
1≤ai,bi,ci,di≤10
输入样例:
3
4 4 2 2
7 4 7 4
3 3 3 3
输出样例:
7
样例解释
有很多种调度方案可以在 7 7 7 个时间单位里完成给定的三个任务,以下是其中的一种方案:
同时运行第一个任务(单 CPU 加上 GPU)和第三个任务(单 CPU),它们分别在时刻 2 2 2 和时刻 3 3 3 完成。
在时刻 3 3 3 开始双 CPU 运行任务 2 2 2,在时刻 7 7 7 完成。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
const int N = 50, M = 210, INF = 0x3f3f3f3f;
int n;
int c[N][3];
int f[2][M][M][M];
int main()
{
cin >> n;
int m = 0, m2 = 0;
for (int i = 1; i <= n; i ++ )
{
int x, y, z, t;
cin >> x >> y >> z >> t;
c[i][0] = x, c[i][1] = z, c[i][2] = min(y, t);
m += x;
if (i % 2) m2 += x;
}
// 注意之前课上的写法有误,之前的写法是m = (m + 1) / 2,因为不一定能恰好分得这么平均
m = max(m2, m - m2); // 这里简单得按奇偶性分成两组即可
memset(f, 0x3f, sizeof f);
f[0][0][0][0] = 0;
for (int u = 1; u <= n; u ++ )
for (int i = 0; i <= m; i ++ )
for (int j = i; j <= m; j ++ )
for (int k = 0; k <= m; k ++ )
{
int& v = f[u & 1][i][j][k];
if (k > j) v = INF;
else
{
register int x = c[u][0], y = c[u][1], z = c[u][2], t = u - 1 & 1;
v = f[u - 1 & 1][i][j][k] + z;
if (i >= x) v = min(v, f[t][i - x][j][k]);
if (j >= x) v = min(v, f[t][min(i, j - x)][max(i, j - x)][k]);
if (i >= y && k >= y)
v = min(v, f[t][i - y][j][k - y]);
if (j >= y && k >= y)
v = min(v, f[t][min(i, j - y)][max(i, j - y)][k - y]);
}
}
int res = INF;
n &= 1;
for (int i = 0; i <= m; i ++ )
for (int j = i; j <= m; j ++ )
for (int k = 0; k <= j; k ++ )
res = min(res, f[n][i][j][k] + max(i, j));
cout << res << endl;
return 0;
}