动态规划--双机调度问题

 问题描述

方法思路

我们可以使用动态规划来解决这个问题。具体思路如下:

  1. 状态表示:使用一个字典 dp,其中键表示处理机A的总处理时间,值表示处理机B的总处理时间。初始状态是处理0个作业时,两台处理机的处理时间均为0。

  2. 状态转移:对于每个作业,我们有两种选择:分配给处理机A或处理机B。对于每个可能的当前状态,我们分别计算这两种选择后的新状态,并更新字典以保留每个A处理时间对应的最小B处理时间。

  3. 结果计算:在处理完所有作业后,遍历所有可能的A处理时间,计算对应的B处理时间,取其最大值中的最小值作为结果。

详细的分析

那怎么想呢?这应该是一个动态规划的问题。动态规划通常用来处理多阶段决策的问题,这里每个作业的分配就是一个决策阶段。每一步决定把当前作业交给A还是B处理,然后根据之前的状态转移过来。

那状态应该怎么设计呢?假设我们有前i个作业已经处理完了,这时候我们需要记录两种状态:处理完前i个作业时,机器A的总处理时间,机器B的总处理时间。这时候总的时间是max(A时间,B时间)。但这样的话,状态量可能很大,因为每个时间都可能是一个状态变量。有没有更好的办法?

或者,我们可以这样考虑:当处理到第i个作业时,其中一个机器的总处理时间可能是另一个机器的总时间加上当前作业的时间。或者说,假设在处理前i个作业后,A的总时间为x,那么B的总时间就是sum_b[i] - x?或者这可能不正确。或者,正确的状态应该是在处理完前i个作业后,机器A的总时间和机器B的总时间之间的关系?

或者,另一个思路是,状态可以表示为处理完前i个作业后,机器A的总时间为x,此时机器B的总时间的最小可能值。或者反过来,状态为处理前i个作业后,机器A的时间为x的情况下,机器B的时间的最小值。或者,可能更有效的是,对于每个i,我们记录机器A和机器B的处理时间差,或者某一方的累计时间。

或者,我们可以把问题转化为:对于每个作业i,选择分配给A或B。那么,当处理到第i个作业时,假设A的总时间是a_total,B的总时间是b_total。对于下一个作业i+1,我们可以选择分配给A或者B。那么,这时候的状态可能由a_total和b_total的组合构成。但这样状态空间会很大,特别是当n很大时,比如每个作业的时间可能很大,总时间总和就会很大,状态数会变得不可行。

不过,题目中的样例给出的时间是较小的,但实际可能的输入可能较大。这时候可能需要一些优化,比如状态压缩或者只记录可能的差异。

或者,我们可以将问题转化为,假设对于前i个作业,A的总处理时间是a,那么此时B的总处理时间的最小可能值是多少。或者反过来。这样,状态可以表示为dp[i][a] = b,其中a是前i个作业中分配给A的那些作业的总时间,而b是此时B的总时间的最小可能值。或者,可能更有效的是,对于每个i,记录所有可能的a值,对应的b的最小值。例如,dp[i][a]表示处理前i个作业,且A的总时间为a时,B的总时间的最小可能值。这样,我们的最终答案就是对于所有可能的a,在i=n时的max(a, dp[n][a])的最小值。

这样的状态转移可能需要考虑每个作业i被分配给A还是B的情况。例如,处理到第i个作业时,如果当前作业分配给A,那么A的时间会增加ai,而B的时间不变。反之,如果分配给B,那么B的时间会增加bi,而A的时间不变。所以,对于每个可能的a值,我们可以从i-1的状态转移而来。

具体来说,初始状态是处理0个作业时,A的时间为0,B的时间为0。即dp[0][0] = 0。然后,对于每个i从1到n,我们遍历所有可能的a值,然后根据当前作业的分配方式更新dp[i][a']的值。

但是,这样的状态空间可能会很大,因为每个作业的a可能增加很多。比如,如果每个ai的值是很大的数,那么n个作业的a的总和可能非常大,导致dp数组无法处理。这时候,可能需要某种优化方式,比如只记录可能的a值的范围,或者用字典来保存可能的a值对应的b的最小值。

比如,可以用一个字典来表示当前状态下的所有可能的a和对应的b的最小值。然后,在处理下一个作业时,生成新的字典项。这样,每一步只需要处理可能的a值,并且合并相同a值下的较小b值。

那么,具体的动态规划步骤如下:

初始化:对于i=0,只有一个状态,即a=0,对应的b=0。

对于每个i从1到n:

遍历当前所有可能的a状态(即上一层的状态)。

对于每个可能的a,有两种选择:

将第i个作业分配给A:

新的a' = a + ai

b' = b (因为作业i分配给A,B的时间不变)

这时候,新的状态是dp[i][a'] = min(当前记录的dp[i][a']的b'值,原b')

或者,将第i个作业分配给B:

a' = a

b' = b + bi

dp[i][a'] = min(当前记录的dp[i][a']的b'值,原b' + bi)

但这里可能需要更仔细的处理。比如,当处理第i个作业时,对于每个状态(a_prev, b_prev),如果分配到A,那么新的a是a_prev + a_i,新的b是 b_prev。反之,分配到B,则新的a是a_prev,新的b是 b_prev + b_i。这时候,对于每个新的状态(a_new, b_new),我们需要记录在i层中,对于每个可能的a_new,对应的b_new的最小值。或者,可能反过来,对于每个a_new,记录最小的b_new,这样当有多个路径到达同一个a_new时,我们只保留较小的b_new,因为对于最终的max(a, b)来说,较小的b_new会带来更好的结果。

例如,假设在处理到某个i时,有两个不同的路径导致a_new=10,而b_new分别为15和20。那么保留15更好,因为max(10,15)=15,而另一个则是20。所以,对于相同的a_new,应该保留较小的b_new。

所以,动态规划的状态转移可以用这样的方式处理:

对于每个i,维护一个字典(或哈希表),其中键是可能的a值,值是该a对应的最小b值。然后,在处理i+1时,遍历当前字典中的每个a和对应的b,生成新的a和b的可能值,并更新下一个字典。

这样,每次处理一个作业时,新的字典中的每个状态都是基于上一层字典中的状态转移而来的两种可能性(选A或选B)。

最终,处理完n个作业后,遍历字典中的所有a和对应的b,计算max(a, b),然后取最小值作为答案。

那这样的话,算法的时间复杂度取决于每个i层可能的a的数量。假设每个a_i和b_i的值都不大,那么字典的大小可能不会太大。比如,样例中的ai总和是2+5+7+10+5+2=31。那么,对于每个i来说,可能的a的范围是0到31。这时候,状态数目是可控的。但如果n很大,或者ai和bi很大,那状态数目可能爆炸。不过,这个问题可能没有更好的办法,只能用这样的动态规划方法。

那么,现在的问题是如何实现这个动态规划。

在Python中,可以用字典或者defaultdict来记录每个i层的状态。初始时,第0层只有{0:0}。然后,对于每个i,从1到n,处理上一层字典中的每个可能的a_prev和对应的b_prev。

例如,对于样例输入:

a = [2,5,7,10,5,2]

b = [3,8,4,11,3,4]

n =6.

初始状态是dp[0] = {0:0}.

处理第1个作业(i=1)时,有两种选择:

分配给A:a_prev=0 +2=2,b_prev=0 → 所以新的a是2,对应的b是0.

分配给B:a_prev=0,b_prev=0+3=3 → 所以新的a是0,对应的b是3.

此时,dp[1]中的状态是{2:0, 0:3}.

处理第二个作业(i=2)时,遍历dp[1]中的每个状态:

第一个状态是a=2,b=0.

分配方式:

如果选A,则a_new=2+5=7,b_new=0.

此时,在dp[2]中,检查是否已经有a=7的记录。如果没有,则记录b=0。如果有,则保留较小的b值。

或者选B,则a_new=2,b_new=0+8=8.

第二个状态是a=0,b=3.

选A的话,a_new=0+5=5,b_new=3.

选B的话,a_new=0,b_new=3+8=11.

所以,处理完i=2后的dp[2]可能有四个可能的a值:

7:0(来自第一个状态选A)

2:8(来自第一个状态选B)

5:3(来自第二个状态选A)

0:11(来自第二个状态选B)

但是,对于相同的a值,如果出现多次,我们只保留最小的b值。例如,假设在某个情况下,同一个a可能对应不同的b,这时候取较小的那个。

比如,假设在处理某个i时,两个不同的转移导致同一个a_new的两个不同的b_new,则只保留较小的那个。

这样,在每一步处理时,对于每一个可能的a_new,我们比较新的b_new是否比已经存在的更小,如果是的话,则更新。

所以,处理完i=2后的dp[2]的各个状态是:

对于a=7,b=0.

a=2,b=8.

a=5,b=3.

a=0,b=11.

然后,对于每个这样的状态,我们继续处理下一个作业。

这样,经过所有的i之后,最终在i=6时的dp[6]中的所有可能的a和对应的b中,取max(a, b)的最小值。

例如,样例的输出是15。那让我们看看如何得到这个结果。

可能的最终状态中,某个a和b的组合的max值是15。比如,假设A的总时间是15,B的总时间是13。那么max(15,13)=15。或者,A的时间是14,B的时间是15,那么max是15。或者两者都是15等等。

现在,如何编写这个动态规划过程的Python代码?

步骤:

1. 读取输入的两行,转化为a和b数组。例如,样例输入的第一行是2 5 7 10 5 2,转化为a数组。第二行是3 8 4 11 3 4,转化为b数组。

2. 初始化一个字典dp,最开始是处理0个作业时的状态:{0:0}。

3. 对每个作业i从0到n-1(假设数组是0-based索引):

a. 创建新的字典tmp_dp,用于存储处理i+1个作业后的状态。

b. 遍历当前dp中的每个a_prev和对应的b_prev。

c. 对于每个这样的a_prev和b_prev,考虑两种选择:

i. 将作业i+1分配给A:

a_new = a_prev + a[i]

b_new = b_prev

此时,检查tmp_dp中是否有a_new的键。如果有的话,比较当前的b_new和已经存在的b值,取较小的。例如,如果原来tmp_dp[a_new]是某个较大的值,而现在的b_new更小,则更新。

ii. 将作业i+1分配给B:

a_new = a_prev

b_new = b_prev + b[i]

同样,处理这个情况,并更新tmp_dp中的a_new对应的b_new的最小值。

4. 将dp更新为tmp_dp,处理下一个作业。

5. 最终,遍历dp中的所有a和对应的b,计算max(a, b),取最小值作为结果。

这样,代码的大体结构应该是这样的。

现在,关于如何高效地处理每个步骤中的字典更新。比如,在Python中,可以用字典的items()遍历当前的a_prev和b_prev,然后对每个可能的分配方式,生成新的a_new和b_new,然后更新到tmp_dp字典中。对于每个新的a_new,如果已经存在,则取最小的b_new值。

例如,在Python中,对于每个可能的a_new和b_new:

if a_new not in tmp_dp or b_new < tmp_dp[a_new]:

tmp_dp[a_new] = b_new

所以,这保证了对于同一个a_new,只保留最小的b_new。

现在,以样例输入为例,我们来看看处理过程:

初始dp={0:0}

处理第一个作业i=0(数组是0-based,所以a[0]=2,b[0]=3):

对于当前状态a_prev=0,b_prev=0:

分配A:a_new=0+2=2,b_new=0 → tmp_dp[2]=0.

分配B:a_new=0 → b_new=0+3=3 → tmp_dp[0]=3.

此时,tmp_dp={2:0, 0:3} → 成为新的dp。

处理第二个作业i=1(a=5,b=8):

遍历当前dp的两个状态:

状态1:a_prev=2,b_prev=0.

分配A → a_new=2+5=7 → b_new=0. 此时,tmp_dp中7的b是0.

分配B → a_new=2 → b_new=0+8=8 → tmp_dp中2的b是8.

状态2:a_prev=0,b_prev=3.

分配A → a_new=0+5=5 → b_new=3.

分配B → a_new=0 → b_new=3+8=11 → 此时,tmp_dp中的0对应的b是11.

现在,比较每个a_new的情况:

对于a=7,tmp_dp[7] =0.

对于a=2,tmp_dp[2]=8.

对于a=5,如果之前tmp_dp中没有5,则添加,b=3.

对于a=0,tmp_dp[0]=11.

所以,此时的tmp_dp包含:

7:0,

2:8,

5:3,

0:11.

处理第三个作业i=2(a=7,b=4):

此时,当前dp是这四个状态。

对于每个状态:

例如,处理状态a_prev=7,b_prev=0:

分配A → a_new=7+7=14 → b_new=0.

分配B → a_new=7 → b_new=0+4=4.

此时,在tmp_dp中:

对于a=14 → b=0.

a=7 → b=4.

接着处理状态a_prev=2,b_prev=8:

分配A → a_new=2+7=9 → b_new=8.

分配B → a_new=2 → b_new=8+4=12.

此时,比较是否已有这些a值。比如,假设tmp_dp现在可能包含a=9,b=8,或 a=2,b=12.

接着处理a_prev=5,b_prev=3:

分配A → a_new=5+7=12 → b_new=3.

分配B → a_new=5 → b_new=3+4=7.

然后处理a_prev=0,b_prev=11:

分配A → a_new=0+7=7 → b_new=11. 现在,当前tmp_dp中的a=7可能已经有值。比如,之前的处理中,当处理a_prev=7,分配B时,得到a=7,b=4。现在,如果此时分配A,得到的a_new=7,b_new=11,那么比原来的b=4大,所以不会被保留。所以,对于a=7,当前的tmp_dp中保持b=4。

分配B → a_prev=0 → b_new=11+4=15 → a_new=0,b_new=15.

此时,tmp_dp中可能有很多状态。例如,对于每个可能的a_new,保留最小的b_new。

这显然会变得比较复杂,但动态规划会逐步处理。

这样,经过所有处理步骤后,最终在第6个作业处理后的字典中找到最小的max(a,b)。

 代码python

a = list(map(int, input().split()))
b = list(map(int, input().split()))
n = len(a)

# 初始化动态规划字典,键为A的总时间,值为B的总时间
dp = {0: 0}

for i in range(n):
    ai = a[i]
    bi = b[i]
    tmp_dp = {}
    for a_prev, b_prev in dp.items():
        # 分配当前作业到A
        new_a = a_prev + ai
        new_b = b_prev
        if new_a not in tmp_dp or new_b < tmp_dp[new_a]:
            tmp_dp[new_a] = new_b
        # 分配当前作业到B
        new_a_b = a_prev
        new_b_b = b_prev + bi
        if new_a_b not in tmp_dp or new_b_b < tmp_dp[new_a_b]:
            tmp_dp[new_a_b] = new_b_b
    dp = tmp_dp

# 找出所有可能中最大的时间的最小值
min_time = min(max(a_total, b_total) for a_total, b_total in dp.items())
print(min_time)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值