1. 程式人生 > 其它 >兼職 - Freelancer - 完全揹包& (差分陣列 or Indexed Tree)

兼職 - Freelancer - 完全揹包& (差分陣列 or Indexed Tree)

兼職

時限:最多 50 個用例,1 秒 (C/C++),1.5 秒 (Java)

S公司有N名全職員工。公司已經定製好了整個業務的工作日程,每位員工都被安排了工作,並且每個員工處理業務時,一次只能做一項工作。所以當業務日程重複了N次以上時,S公司就需要聘請兼職來工作。任何人都可以在中途接手另一個人的工作,這時的工作交接所耗費的時間和成本忽略不計。當聘請兼職時,如果同時跟多個人簽訂合約,那麼提供兼職的公司會給予一定的費用折扣。

當給出了S公司的全職員工數量、整個業務日程,以及每日需要給兼職支付的費用資訊時,想把兼職的僱用費用弄成最低。

當給出如上所示的費用折扣資訊時,如果S公司要聘請4個兼職,那麼最優的方式是採用2 + 2,這時費用只需 38,000 韓元。如果業務日程資訊如上所示,可以採用下面的方式開展工作。

如上所示,第1天需要1個人、第2天需要3個人、第3-4天需要4個人、第5天需要3人、第6天需要2個人、第7-8天需要1個人、第10-15天需要1個人。假設有2名全職員工,那麼可以在第1、6-8、10-15天,只由全職員工處理業務,在第2天聘請1個兼職,費用為10,000韓元,在第3-4 天每天聘請2個兼職,費用為38,000韓元,在第5天聘請1個兼職,費用為10,000韓元。這樣支付的就是最低兼職費用58,000韓元。

當給出S公司的全職員工數量、整個業務日程,以及每日給兼職支付的費用時,請找出給兼職支付的最少費用。

[限制條件]

1. 全職員工數量N為介於0到100,000之間的整數。

2. 業務日程數量M為介於1到200,000之間的整數。

3. 業務日程會給出開始日期和結束日期。如果日程之間的結束日期和開始日期之間有重合,則表示需要兩個人。

4. 業務日程的開始日期S和結束日期E是介於1到100,000之間的整數.

5. 給兼職支付的費用C是介於10,000到100,000,000之間的整數。

6. 一個日程的業務能以一天為單位由多個人分工完成。

7. 任務安排完後,不應有多餘的兼職。

8. 僱傭資訊中一定會提供聘請一名兼職的費用。

[輸入]

首先給出測試用例數量 T,接著給出 T 種測試用例。

每個測試用例的第一行空格區分給出全職員工數量N、業務日程數量M、給兼職支付費用的資訊數量K。()

接下來通過M行,每行空格區分給出該業務日程的開始日期S和結束日期E。() 接下來通過K行給出聘請兼職時每天需要支付的費用資訊,該資訊空格區分給出人數P,需支付的費用C。()

[輸出]

一個測試用例輸出一行。對每個測試用例都輸出“#x”(x為測試用例的編號,從1開始),然後輸出S公司需要支付的最低兼職費用。

[輸入和輸出示例]

(輸入)

3

2 5 5

1 5

2 8

3 6

10 15

2 4

1 10000

2 19000

5 47000

10 90000

150 1300000

1 10 5

1 10

9 10

3 9

6 10

10 10

6 9

3 7

1 6

7 7

1 10

1 10196

3 23895

4 29620

10 53460

15 57180

1 10 6

1 5

5 9

3 7

1 5

4 8

2 6

1 3

2 4

4 7

6 7

1 13354

2 23714

5 58520

10 111670

11 121319

17 185147

(輸出)

#1 58000

#2 324280

#3 391248

解題思路:
如果先不考慮業務日程,求最小費用,每組折扣,可以選擇多個,是個完全揹包問題;
其中,費用種類總和K為物品數量,費用資訊的每組人數為權重w[i],每組費用為物品價值v[i],業務日程數量M為揹包容量;

求每天的工作需要的員工人數有兩種方法:
1.差分陣列
2.Indexed Tree, 用Indexed Tree時,query方法和update方法與平時的相反

package tree;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

/**
 * 如果先不考慮業務日程,求最小費用,每組折扣,可以選擇多個,是個完全揹包問題;
 * 其中,費用種類總和K為物品數量,費用資訊的每組人數為權重w[i],每組費用為物品價值v[i],業務日程數量M為揹包容量;
 * 
 * 求每天的工作需要的員工人數有兩種方法:
 * 1.差分陣列
 * 2.Indexed Tree, 用Indexed Tree時,query方法和update方法與平時的相反
 * @author XA-GDD
 *
 */
public class Freelancer_0322 {
	static int T,N,M,K;
	static int _max_Nval = 100000;
	static int _max_Mval = 200000;
	static int _max_Kval = 100;
	static int [][] work = new int[_max_Mval+1][2];
	static int [][] fee = new int[_max_Mval+1][2];
	static long [] dp = new long[_max_Mval+1];
	static int [] empCnt = new int[_max_Nval+2];
	static int [] idxTree = new int[_max_Nval*4];
	static int offset;
	static long ANS;
	public static void main(String[] args) throws IOException {
		System.setIn(new FileInputStream("D:\\workspace\\sw_pro\\test_case\\sample_input_0322.txt"));
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	    StringTokenizer st = new StringTokenizer(br.readLine());
		T = Integer.parseInt(st.nextToken());
	    for(int testCase = 1; testCase<=T;testCase++) {
	    	st = new StringTokenizer(br.readLine());
	    	N = Integer.parseInt(st.nextToken());
	    	M = Integer.parseInt(st.nextToken());
	    	K = Integer.parseInt(st.nextToken());
	    	
	    	Arrays.fill(empCnt, 0);
	    	Arrays.fill(dp, 1,M,Long.MAX_VALUE);
	    	Arrays.fill(idxTree, 0);
	    	for(int i=0;i<K;i++) {
	    		Arrays.fill(fee[i], 0);
	    	}
	    	ANS=0L;
	    	
	    	//業務日程	
	    	int maxDate = 0;
	    	for(int i=0;i<M;i++) {
	    		st = new StringTokenizer(br.readLine());
	    		
	    		int startDate = Integer.parseInt(st.nextToken());
	    		int endDate = Integer.parseInt(st.nextToken());
	    		maxDate = Math.max(maxDate, endDate);
	    		
	    		
	    		//方法1:差分陣列 ======start=========
//	    		empCnt[startDate]++;
//	    		empCnt[endDate+1]--;
	    		//方法1:差分陣列 ======end=========
	    		
	    		//方法2:Indexed Tree ======start=========
	    		work[i][0] = startDate;
	    		work[i][1] = endDate;
	    		//方法2:Indexed Tree ======end=========
	    	}
	    	
	    	//方法1:差分陣列 ======start=========
//	    	for(int i=1;i<=maxDate;i++) {
//	    		empCnt[i] += empCnt[i-1];
//	    	}
    		//方法1:差分陣列 ======end=========
	    	
	    	//費用
	    	for(int i=0;i<K;i++) {
	    		st = new StringTokenizer(br.readLine());
	    		fee[i][0] = Integer.parseInt(st.nextToken());
	    		fee[i][1] = Integer.parseInt(st.nextToken());
	    	}
	    	
	    	//完全揹包
	    	for(int i=0;i<K;i++) {
	    		for(int j=fee[i][0];j<=M;j++) {
	    			dp[j] = Math.min(dp[j], dp[j-fee[i][0]]+fee[i][1]);
	    		}
	    	}
	    	
	    	//方法1:差分陣列 ======start=========
//	    	for(int i=0;i<=maxDate;i++) {
//	    		ANS += empCnt[i]>N? dp[empCnt[i]-N]:0;
//	    	}
	    	//方法1:差分陣列 ======end=========
	    	
	    	
	    	//方法2:Indexed Tree ======start=========
	    	int k=0;
	    	while((1<<k)<maxDate) {
	    		k++;
	    	}
	    	offset=1<<k;
	    	for(int i=0;i<M;i++) {
	    		update(offset+work[i][0],offset+work[i][1]);
	    	}
	    	for(int i=1;i<=maxDate;i++) {
	    		int currEmpCnt = query(offset+i);
	    		ANS += currEmpCnt>N? dp[currEmpCnt-N]:0;
	    	}
	    	//方法2:Indexed Tree ======end=========
	    	
	    	System.out.printf("#%d %d\n",testCase,ANS);
	    }
	    	
	}
	
	static void update(int start ,int end) {
		while(start<=end) {
			if(start%2==1) {
				idxTree[start]++;
			}
			if(end%2==0) {
				idxTree[end]++;
			}
			start=(start+1)>>1;
			end = (end-1)>>1;
		}
	}
	
	static int query(int index) {
		int res=0;
		while(index>0) {
			res += idxTree[index];
			index = index>>1;
		}
		return res;
	}

}