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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值