Poj 1973 Software Company(二分+并行DP)

题意:软件公司接了两个项目,来自同一个合同,要一起交付。该公司有n个程序猿来做这两个项目A和B,每个项目都被分为m个子项目,给定每个程序猿做一个A中的子项目需要的时间Xi秒,和做B中的子项目所需时间Yi秒,问最短需要多少时间可以完成这两项工程。

分析:显然同一时刻不同的工人可以一起工作,那么面临的问题也就是一个工作的分配,让某一时刻的某个工人干A还是干B,来使AB尽快都完成。刚始做的时候确实没有什么头绪,后来看了大牛们的解题报告,大多是二分时间判断再加上并行DP来解决这个问题。但是仔细分析的好像特别少,想我这种菜鸟真是看不懂。隔了两天,还是硬着头皮开始一步步分析他们的思路。

题解:二分时间呢,就是枚举时间,然后验证看看在这个时间内能否完成这两个项目。如果满足就用二分法进一步缩小范围,直到求得最精确地结果。这里也涉及到一个时间上限的问题,大家都知道,二分法在初始的时候有下限和上限两个值,下限很简单就设为1,上限要看题目的要求和测试数据的强弱了。这道题呢,上限设为30000就可以通过了,但是这里还是用动态获得上限为好。也就是根据输入数据求得上限,这个上限就是完成这两个项目的最长时间,那么我们可以求得所有程序猿中完成单个子项目的最大值Max,再乘以子项目的总数2m就是粗略的上限了。这里还可以优化,因为我们求得是最短的时间,肯定大大低于这个上限。如Max / (n+1) *2m。

       还有一个就是DP的状态转移方程的确定了,首先,dp[ i ][ j ]表示前 i 个人在完成 j 个A子项目时最多能完成的B子项目的个数,最后只要判断dp[n][m]是否大于m就可以了,如果大于则满足要求,小于则不行。状态转移方程 为 dp[ i ][ j ] = max(dp[ i ][ j ], dp[ i-1 ][ j-k ] + (time-k * x[i] ) / y[i] );这个方程呢,之前一直没有理解,就是因为没有意识到这个是并行的运算。有句话说的好“时间对每个人都是公平的”,在这里也是一样,每个程序猿都有一个time,他可以选择在这个时间里做多少个A子项目和多少个B子项目,使得开发的进度满足公司的整体需求。这个转换方程求得是前 i 个人完成了j个A项目时,最多能完成的B子项目 。那么这就取决于第 i 个人的选择,如果第 i 个人完成 k 个A子项目,那么前 i-1 个人就应该完成了 j-k个A子项目。然而谁也不知道这个该死的第 i 个程序猿会做多少个A子项目啊,所以我们只能从0到m假设他做的A子项目,从中找到最短的时间。然后再告诉他应该做的确切的个数,服从公司的整体需求。这个问题用到了一个子循环可以解决。这里还可以有一个优化,在给定的时间下第 i 个程序猿可能做不了m个A子项目,最多只能做Time/a[i]个。

         这里还有一个很重要的优化,就跟0-1背包的空间优化差不多。0-1背包时,把二维的dp数组用一维数组实现了,这里可以参照这种方法,用一维数组实现dp.

import java.util.Scanner;

public class Main{
	static int  N =102;
	static int[] a=new int[N];
    static int[] b=new int[N];
	static int[] dp=new int[N];
	static int n,m;
	static boolean judge(int time){ 
		for(int i=0;i<N;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(a[i]*k <= time && j+k <= m)
	                         dp[j+k]=Math.max(dp[j+k],dp[j]+(time-a[i]*k)/b[i]);
	                 }
	             }
	        }
	  return dp[m] >= m;
	}
	 
  public static void main(String args[]){
	 Scanner sc=new Scanner(System.in);
	 int cas=sc.nextInt();
	 while(cas--!=0){
	    n=sc.nextInt();
	    m=sc.nextInt();
	    int max_time=-1;
        for(int i=0;i<n;i++){
        	a[i]=sc.nextInt();
        	b[i]=sc.nextInt();
        	max_time=Math.max(max_time,a[i]);
	        max_time=Math.max(max_time,b[i]);
        }
        max_time=max_time*m*2;
        int left,right,mid;
        int min_time=0;
        left=1;
        right=max_time;
        while(left <= right){
            mid=(left+right)>>1;
            if(judge(mid)){
                right=mid-1;
                min_time=mid;
            }
            else
                left=mid+1;
        }
	 System.out.println(min_time);
    }
  }
}


版权声明:本文为博主原创文章,未经博主允许不得转载。

posted @ 2013-08-28 21:34  InkGenius  阅读(213)  评论(0编辑  收藏  举报