【动态规划】任务调度dp 自用

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 1s1,s2,s3,s420

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 1A1,A2,,As1,B1,B2,,Bs2,C1,C2,,Cs3,D1,D2,,Ds460

解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 1n6×103 0 ≤ t 1 , t 2 , t 3 ≤ 5 0\le t_1,t_2,t_3\le 5 0t1,t2,t35

解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。

对于每个任务,你可以为它分配不同的硬件资源:

  1. 在单个 CPU 上运行。
  2. 在两个 CPU 上同时运行。
  3. 在单个 CPU 和 GPU 上同时运行。
  4. 在两个 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 1n40,
1 ≤ a i , b i , c i , d i ≤ 10 1 \le a_i,b_i,c_i,d_i \le 10 1ai,bi,ci,di10

输入样例:
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;
}
  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值