POJ 1973

问题描述:1、一家公司同时进行两个完全相同的项目A和B。每个项目由若干个子项目组成。
2、公司有若干名程序员,一个程序员同一时间只能进行一个子项目。相同项目,可由多个程序员并行完成多个子项目。两个完全相同的项目,可并行完成。
3、 只有两个项目都完成后,才算完成,其中一个项目完成不算完成。


输入:1、输入数字t,表示测试用例数
2、输入n, m,分别表示程序员数量和每个项目中的子项目数量。
3、输入每个程序员完成项目A和项目B所需要的时间:a[i], b[i]

输出:完成两个项目所需要的最少时间Time


例如:输入:
1
3 20
1 1
2 4
1 6
输出:18

1. 从问题入手

 这道题问的是:完成两个项目用的最短时间。我们可以从两个方向思考。一是通过对程序员进行不同组合,寻找到完成A和B两个项目的最短时间;另一个是,遍历时间,找到一个最短时间,此时AB项目均可以完成。由于后者将动态规划中的一个变量确定下来,按照控制变量的思想,我们选择后一种思路进行进一步分析。

2. 变量分析

  该问题涉及到以下几个变量的动态分析:完成两个项目的时间、完成A项目的数量、完成B项目的数量、所需要的程序员数量。动态规划的两个重要问题是:状态和状态转移。状态有上面的四个变量来确定,每个确定的值对应一种确定的状态。而“状态转移”就是通过修改上面的4个变量,实现一种状态转换到另一种状态。
  我们可以定义一个函数关系:f(t, i, j) = h。其中t 为时间,i为程序员数量,j 为完成A项目中子项目的数量,h为完成B项目中子项目的数量。那么需要对t, i, j,三个变量的可能值进行枚举遍历,从而h值相应地进行变化。这样,我们就可以定义状态了。f(t, i, j)=h表示,在t时间内,前i个人完成了j个A子项目的时候,最多可以完成h个B子项目。当j>=m && h>=m时,且t最小时,即完成任务。
  
  对于时间来说,由于需要遍历所有时间,所以有两点需要考虑:
  (1)时间的上下限。对于下线,最差为0。对于上限,假设每个项目共有m个子项目,则找到做A项目最慢的程序员i,则A项目最长需要a[i]*m。找到做B项目最慢的程序员j,则B项目最长时间需要b[j]*m,则完成两个项目需要的最长时间为:

2 * m * max( a[i] , b[j])

  (2)遍历方法。最简单的方法是,从t=0开始遍历,找到最短的时间。但是这样效率会比较低。由于时间是顺次由短变长依次遍历,因此采用二分查找效率更高。

  当时间确定下来后,就只剩下两个两个自变量:参与项目的程序员数量i、完成A子项目的数量j。而因变量h随着i, j的变化而变化。即可得到状态方程f(时间常数,i, j)=h。该方程即状态方程。描述为:在时间内,i名程序员在完成i个A子项目时,最多可以完成多少个B 子项目。
  
 状态确定了,就可以确定状态转移方程了。

3. 状态转移

对于i个程序员来说,在时间time下,前i-1个程序员可以完成j个A子项目,而第i个程序员可以完成k个A子项目。则可以有如下关系:
项目数量

其中dp[i][j+k]为位置值,在时间time范围内,前i个程序员完成j+k各A子项目,最多可以完成B子项目的数量。在i,j确定的情况下,由于我们无法知道在k取何值时,dp[i][j+k]可以取得最大值,因此需要对k可能的值进行遍历。从而求出dp[i][j+k]。于是,就得到了下面的迭代方程:

dp[i][j+k] = max (dp[i][j+k], dp[i-1][j] + (time- a[i] * k)/b[i])

这样,我们就能找到在时间time下, 前i个人完成j+k个A子项目的情况下,最多可完成B子项目的数量。

4. 关于优化

  参考了几个博文中,关于0-1背包空间压缩的问题。感觉有点说的不明不白。此处再详细说一下。
  我们知道,对于0-1背包问题,用矩阵存放是为了方便寻找最优解路径。当我们不需要最优解,只需要一个结果时,可以用一个一维数组来存放最优解。
  而用一维数组存放最优解有一个问题。如下图所示,当采用二维数组的时候,计算方向为从
  背包方向
左上角开始,从左向右,从上到下进行延伸。使得右下角的值为最优解。在此运算中,对于某一个单元格的值,该值收到其上面单元格和左上侧单元格的影响。在初试完第一行的数值后,从第二行开始,无论运算方向是从左向右,还是从右向左,每个单元格的值都需要用到上方和左上方单元格的值。
  但是,当用一维数组存储最优解时,从矩阵的第二行开始,如果计算顺序从左向右进行,则导致矩阵上一行的数据被覆盖。因此,从第二行开始,我们需要从右向左进行计算。
  同理,对于这道题,也是这个原理。

5. 程序代码

#include <iostream>
#include <algorithm>
using namespace std;

void inputCase(int &n, int &m, int *a, int *b)
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
        cin>>a[i]>>b[i];
}

void findMaxTime(int n, int m, int *a, int *b, int &maxTime)
{
    int aMax= a[0], bMax= b[0];
    for(int i=0;i<n;i++)
    {
        if( aMax < a[i])
            aMax = a[i];
        if( bMax < b[i])
            bMax = b[i];
    }
    maxTime = max(aMax, bMax) * m *2;
}

bool judge(int time, int n, int m, int *a, int *b)
{
    int *dp=new int[m+1];
    for(int i=0;i<=m;i++)
        dp[i]=-1;
    dp[0]=0;

    for(int i=0;i<n;i++)
        for(int j=m;j>=0;j--)
            if(dp[j]!=-1)
                for(int k=m;k>=0;k--)
                    if(j+k <= m && a[i]*k <=time)
                        dp[j+k] = max(dp[j+k], dp[j]+(time-a[i]*k)/b[i]);
    int maxB=dp[m];
    free(dp);

    return (maxB>=m);
}

int findMiniTime(int n, int m, int* a, int *b, int maxTime)
{
    int left=0, right=maxTime, mid;
    int minTime;
    while(left <= right)
    {
        mid = (left+right)/2;
        if(judge(mid, n, m, a, b)) // time enough
        {
            right = mid-1;
            minTime=mid;
        }
        else                        // time absent
            left = mid+1;
    }
    return minTime;
}


int main(int ac, char **av)
{
    int t, n, m, maxTime;
    int a[102],b[102];

    cin>>t;
    while(t > 0)
    {
        t--;

        inputCase(n, m, a, b);                        ///> 输入测试用例
        findMaxTime(n, m, a, b, maxTime);              ///> 寻找该测试用例的时间上限
        cout<<findMiniTime(n, m, a, b, maxTime)<<endl;  ///> 使用二分法,寻找最少的使用时间
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhizifengxiang/article/details/53036726
POJ