描述
在經過一段時間的經營後,dd_engi的OI商店不滿足於從別的供貨商那裏購買產品放上貨架,而要開始自己生產產品了!產品的生產需要M個步驟,每一個步驟都可以在N臺機器中的任何一臺完成,但生產的步驟必須嚴格按順序執行。由於這N臺機器的性能不同,它們完成每一個步驟的所需時間也不同。機器i完成第j個步驟的時間爲T[i,j]。把半成品從一臺機器上搬到另一臺機器上也需要一定的時間K。同時,爲了保證安全和產品的質量,每臺機器最多隻能連續完成產品的L個步驟。也就是說,如果有一臺機器連續完成了產品的L個步驟,下一個步驟就必須換一臺機器來完成。現在,dd_engi的OI商店有史以來的第一個產品就要開始生產了,那麼最短需要多長時間呢?
某日Azuki.7對躍動說:這樣的題目太簡單,我們把題目的範圍改一改
對於菜鳥躍動來說,這是個很困難的問題,他希望你能幫他解決這個問題
格式
輸入格式
第一行有四個整數M, N, K, L
下面的N行,每行有M個整數。第I+1行的第J個整數爲T[J,I]。
輸出格式
輸出只有一行,表示需要的最短時間。
限制
1s
提示
對於50%的數據,N<=5,L<=4,M<=10000
對於100%的數據,N<=5, L<=50000,M<=100000
思路: DP + 單調隊列
先不考慮時間和數據規模問題。
狀態dp[i][j] 表示 完成前j步驟, 在第i部機器, 完成產品的步驟j所花費的最少時間。
sum[i][j]: 表示 機器i, 連續完成前j步驟需要的時間
狀態轉移方程: dp[i][j] = min(dp[i][j], dp[p][q] + (sum[i][j] - sum[i][q]) + k) (i != p),(j - l<= q < j)
也就是說: 前j個步驟的最優解 肯定會在, 前q步驟的最優解 + 該機器完成[q + 1, j]步驟 + 轉移時間k。
而對於每個 dp[i][j] 都有4個參數 dp[p][q], sum[i][j], sum[i][q], k;
而k, sum[i][j]對於每個dp[i][j]是特定的, 也就是常量。
所以只需要找到 x = min(dp[p][q] - sum[i][q]) 然後 dp[i][j] = min(dp[i][j], x + k + sum[i][j]);
現在要引入 dp2[i][j]表示 完成前j步驟, 但是不在第 i 部機器, 完成產品的步驟j,花費的最少時間。 並且 [j + 1, m] 步驟一定要在機器i完成
dp2[i][j] = min(dp2[i][j], dp[p][j]); (i != p)
所以 dp[i][j] = min(dp[i][j], dp2[i][q] + k + (sum[i][j] - sum[i][q]) (i != p) (j - l <= q < j)
還有一個問題~ 最優解不一定需要轉移。
而轉移方程最少會算入一個k。
所以需要有 第0步驟。dp2[i][0] = 0; (1 <= i <= 5) 並且轉移時間k, 由dp2[i][j]負責, 這樣就會避免一定要轉移。
還有, 把sum[i][q] 歸到 dp2[i][q]裏面, 因爲下標一樣, 方便計算。
現在dp2[i][j] = min(dp2[i][j], dp[p][j]) + k - sum[i][j];
dp[i][j] = min(dp[i][j], dp2[i][q] + sum[i][j]); (j - 1 <= q < j)
=min(dp[i][j], dp2[i][q]) + sum[i][j];
考慮時間問題。
遍歷每個步驟需要 M, 遍歷dp[i][q] 需要 N * min(q, l), 完成 dp2需要 N * (N - 1)次.
複雜度是O(M * N * L) > O(M * N * N);
對於dp[i][j] 和 dp[i][j + 1] 來說 都是從 dp2[i][p] 中找最小值。 只是區間錯開了一個單位。
也就是說 dp[i][j] 找的是 dp2[i][q] q 在區間[j - l, j) 而 dp[i][j + 1] 是 [j - l + 1, j + 1);
需要用單調隊列完成該任務。 用O(1)的時間來獲得 已知的dp2[i][~]的最小值.
也就是隻要對每部機器去維護好一個單調遞增的隊列。就能dp[i][q] 優化爲O(1).
總的複雜度是O(M * N * N) > O(M * N);
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
//單調性DP(DP + 單調隊列優化)
// 狀態轉移方程: dp[i][j] = min(dp[i][j], dp[p][q] + (sum[i][j] - sum[i][q]) + k) (i != p)
const int V = 100000 + 50;
const int MaxN = 80 + 5;
const int mod = 10000 + 7;
const __int64 INF = 0x7FFFFFFFFFFFFFFFLL;
const int inf = 0x7fffffff;
int dp1[6][V]; //dp1[i][j] 表示完成前j步驟,並且第i個機器完成第j步驟花費的最少時間
int dp2[6][V]; //dp2[i][j] 表示完成前j步驟,不在第i個機器完成第j步驟的花費,並且下次只能在i機器完成。
int sum[6][V]; //sum[i][j] - sum[i][k] 表示 機器i完成(k + 1,j)步驟所花的時間
int Que[6][V], front[6], rear[6]; //優化 dp2[i][j] - sum[i][j] + k;
int m, n, k, l;
int getFront(int i, int j) {
while(front[i] < rear[i] && Que[i][front[i]] < j - l) //去掉過期的最小值
front[i]++;
return dp2[i][Que[i][front[i]]];
}
void pushBack(int i, int j) {
while(front[i] < rear[i] && dp2[i][Que[i][rear[i] - 1]] > dp2[i][j])
rear[i]--;
Que[i][rear[i]++] = j;
}
int main() {
int i, j, p;
scanf("%d%d%d%d", &m, &n, &k, &l);
for(i = 1; i <= n; ++i) {
for(j = 1; j <= m; ++j) {
int temp;
scanf("%d", &temp);
sum[i][j] = sum[i][j - 1] + temp;
}
pushBack(i, 0);
}
for(j = 1; j <= m; ++j) {
for(i = 1; i <= n; ++i)
dp1[i][j] = getFront(i, j) + sum[i][j];
//維護單調隊列 爲下次dp1做準備
for(i = 1; i <= n; ++i) {
dp2[i][j] = inf;
for(p = 1; p <= n; ++p)
if(p != i)
dp2[i][j] = min(dp1[p][j], dp2[i][j]);
dp2[i][j] = dp2[i][j] - sum[i][j] + k;
pushBack(i, j);
}
}
int ans = dp1[1][m];
for(i = 2; i <= n; ++i)
ans = min(ans, dp1[i][m]);
printf("%d\n", ans);
}