zoukankan      html  css  js  c++  java
  • 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);
        }
      }
    }
    


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

  • 相关阅读:
    Android触控屏幕Gesture(GestureDetector和SimpleOnGestureListener的使用教程)
    Cocos发展Visual Studio下一个libcurl图书馆开发环境的搭建
    使用DbUtils实现CRUD
    大约apache 2.4.X虚拟主机配置问题的版本号后,
    应对黑客攻击SQL SERVER数据库中的一个案例
    【Unity技能】做一个简单的NPC
    ViewRootImpl和WindowManagerService笔记
    Web Service简单入门示例
    Oracle Instanc Client安装命令工具
    ListView嵌套GridView显示不完整的解决方案
  • 原文地址:https://www.cnblogs.com/AndyDai/p/4734114.html
Copyright © 2011-2022 走看看