VIJOS P1243 生產產品

描述

在經過一段時間的經營後,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]。

輸出格式

輸出只有一行,表示需要的最短時間。

樣例1

樣例輸入1[複製]

3 2 0 2
2 2 3
1 3 1

樣例輸出1[複製]

4

限制

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);
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章