算法导论 第三版 动态规划之库存规划

15-11

题目:某公司的额定产能是每月生产m台设备,而如果每月生产超过m台,则需要额外雇佣劳动力,每多生产一台设备所需的雇佣成本为c。已知未来n个月每个月的需求为d[i],不同的月份需求不一样,但是具体到某个月需求是确定的。另外,如果每个月末有设备剩余,则需要付出h(j)的库存成本,j是当月的库存,h(j)是单调非递减函数。安排每个月的生产计划,使得在满足需求的前提下成本最小。


解答:题目中对公司额定产能m的意思应该是每个月最多可以生产m台,没有必要一定要生产m台(否则答案是确定的了。)

考虑每个月生产的设备可以小于m台。考虑前i个月的情况。

令p[i][j] 表示仅考虑前i个月时,第j个月生产的设备台数。v[i]表示按照p[i][j]的生产计划,前i个月的成本,包括雇佣成本e[i]和库存成本s[i]。令D[j]表示前j个月的总需求量,Q[i][j]表示仅考虑前i个月时,前j个月总生产的设备台数。令W[i]表示仅考虑前i个月时,所有月生产设备数小于m台的月份里,m与该月生产设备数之差的总和。

一些要满足的条件:(a)D[i] == Q[i][i],即前i个月的需求总和与前i个月的生产总和是相等的。首先为保证需求得到满足,D[i] <= Q[i][i],而如果Q[i][i] > D[i],则第i个月要付出的库存成本为h(Q[i][i] – D[i]) >= 0,为最小化成本,需要满足条件:D[i] == Q[i][i]。(b)当每台设备的雇佣成本与每台设备的边际库存成本接近或者前者大于后者时,在已经求出的前i个月生产计划的基础上,计算前i+1个月的生产计划时,若d[i+1] > m,则需要将部分设备提前生产,而所额外生产的月份满足在前i个月的生产计划中该月的生产数不足m,而若超过m,则付出的库存成本会增加。

首先考虑第一个月:p[1][1] = d[1]。因为生产之前没有库存,所以第一个月的需求量即是第一个月的计划生产量。假设已知前i个月的生产计划p[i][j],当加入了第i+1个月的需求时,分两种情况考虑:1)d[i+1] <= m,则前i个月的计划不变,第i+1个月的生产计划为d[i+1]台。2)d[i+1]>m,令N = d[i+1]-m,令min = min{N, w[i]},则在考虑前i+1个月的生产计划时,可以将第i+1个月需求中的k台设备,交由前i个月的部分月份生产,其中0 <= k <= min,分别计算k不同值时v[i+1]的值,当v[i+1]最小时,得出k值和第i+1个月的生产计划。将k台设备从i到0向每个月计划生产数不足m个月份里填充,得出p[i+1][j]。


附Java源码:

public class InventoryProgramming {
	private int[] d;	//每个月的需求
	private int m;		//每个月的产能
	private int c;		//雇佣生产一件的单价
	private int[] h;	//库存费用
	private int[][] p;	//p[i][j] 表示只考虑前i个月时,第j个月的产量 从第0个月开始算
	private int[][] pp;	//临时存放p[][]
	private int[] w;	//w[i]表示只考虑前i个月时,月生产不足m件的那些月距离m件的个数和
	private int[] v;	//v[i]表示只考虑前i个月时的总消耗
	
	public InventoryProgramming(int mm, int cc, int[] hh, int[] dd) {
		m = mm;
		c = cc;
		d = new int[dd.length];
		h = new int[hh.length];
		for (int i = 0; i < dd.length; i++) {
			d[i] = dd[i];
		}
		for (int i = 0; i < hh.length; i++) {
			h[i] = hh[i];
		}
		p = new int[d.length][d.length];
		pp = new int[d.length][d.length];
		w = new int[d.length];
		v = new int[d.length];
		
		runInventoryPrgm();
	}
	
	public void displayResutl() {
		for (int i = 0; i < d.length; i++) {
			System.out.print(p[d.length - 1][i] + " ");
		}
		System.out.println("\n" + v[d.length - 1]);
	}
	
	private void runInventoryPrgm() {
		int N, temp;
		p[0][0] = d[0];
		v[0] = value(0, p);
		for (int i = 1; i < d.length; i++) {
			w[i - 1] = cmpW(i - 1);
			if(d[i] <= m) {
				p[i][i] = d[i];
				for (int j = 0; j < i; j++) {
					p[i][j] = p[i-1][j];
				}
				v[i] = value(i, p);
			}
			else {
				N = d[i] - m;	//N > 0
				v[i] = Integer.MAX_VALUE;
				int min = Math.min(N, w[i - 1]);
				for (int k = 0; k <= min; k++) {
					setPP(i, k);
					pp[i][i] = N - k + m;
					temp = value(i, pp);
					if (v[i] > temp) {
						v[i] = temp;
						for(int j = 0; j <= i; j++) {
							p[i][j] = pp[i][j];
						}
					}
				}
			}
		}
	}
	
	private void setPP(int i, int k) {
		//用第前i-1个月的计划,临时填充到pp[i]的前i-1里,增加量为k,k <= w[i-1]
		int dis = 0;
		for(int j = i-1; j >= 0; j--) {
			if(k > 0) {
				if (p[i - 1][j] < m) {
					dis = Math.min(k, m - p[i-1][j]);
					pp[i][j] = p[i - 1][j] + dis;
					k -= dis;
				}
				else {
					pp[i][j] = p[i-1][j];
				}
			}
			else {
				pp[i][j] = p[i-1][j];
			}
		}
	}
	private int cmpW(int i) {
		int res = 0;
		for (int j = 0; j <= i; j++) {
			if (p[i][j] < m) {
				res += (m - p[i][j]);
			}
		}
		return res;
	}
	
	private int value(int i, int[][] prod) {
		// 前i个月生产的总成本
		return employCost(i, prod) + inventoryCost(i, prod);
	}
	
	private int employCost(int i, int[][] prod) {
		//前i个月,雇佣生产的费用,包括第0和第i个月
		int res = 0;
		for(int j = 0; j <= i; j++) {
			res += c * (prod[i][j] > m ? prod[i][j] - m : 0);
		}
		return res;
	}
	
	private int sigmaProduce(int i, int j, int[][] prod) {
		//前i个月的计划里,前j个月累计的生产数
		int res = 0;
		for(int k = 0; k <= j; k++) {
			res += prod[i][k];
		}
		return res;
	}
	private int sigmaDemands(int i) {
		int res = 0;
		for(int j = 0; j <= i; j++) {
			res += d[j];
		}
		return res;
	}
	
	private int inventoryCost(int i, int[][] prod) {
		//前i个月,累计库存的费用
		int res = 0;
		for (int j = 0; j < i; j++) {
			res += h[sigmaProduce(i, j, prod) - sigmaDemands(j)];
		}
		return res;
	}
	
}
点赞