描述
在经过一段时间的经营后,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);
}