演算法導論 第三版 動態規劃之庫存規劃
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;
}
}