Problem:任务安排
有N个任务排成一个序列在一台机器上等待执行,顺序不得改变。要把N个任务分成若干批,每一批当然是连续的若干个任务。从0时刻开始加工(只有一个机器,单线程),执行第i个任务所需时间为T[i]。在每批开始前,机器需要S的时间来开机。且一批中的任务是等这一批全被处理完才一起结束。易得,若j属于第x批任务(k为这批任务的最后一个),那他要等的时间为S*j + sum(1 ~ k)。每个任务有费用系数C[i],所花费用为等待时间*C[i]。请规划一个分批方案,使得费用最小。
我们为了方便表述,定义sumT,sumC,为T,C的前缀和。
Task 1(tyvj1098)
数据范围:1 ≤ N ≤ 5000,1 ≤ S ≤ 50,1 ≤ T[i],C[i] ≤ 100。
解法一:
F[i][j]表示把前i个任务分成j批的最小费用,则有转移方程:
F[i][j] = min{F[k][j - 1] + (S * j + sumT[i]) * (sumC[i] - sumC[k])} k∈[0, i) 复杂度O(n³)。
明显无法得到满分。
解法二:
解法一中我们把用到了一个参数,表示批数,就是为了给乘S提供系数。事实上,我们每次新建一批任务,都可以给后面的累加上一倍的S,省去了这一维,把复杂度优化到O(n²)。
F[i] = min{F[j] + sumT[i] * (sumC[i] - sumC[j]) + S * (sumC[N] - sumC[j])} j∈[0, i)
这是一种名为“费用提前计算”的经典思想。
long long f[5010], sumt[5010], sumc[5010]; int n, s; int main() { scanf("%d%d", &n, &s); for(int i = 1; i <= n; ++i) { int t, c; scanf("%d%d", &t, &c); sumt[i] = sumt[i - 1] + t; sumc[i] = sumc[i - 1] + c; } memset(f, 0x3f, sizeof(f)); for(int i = 1; i <= n; ++i) for(int j = 0; j < i; ++j) f[i] = min(f[i], f[j] + sumt[i] * (sumc[i] - sumc[j]) + s * (sumc[n] - sumc[j])); printf("%lld", f[n]); return 0; }
Task 2(IOI2002/POJ1180)
数据范围:1 ≤ N ≤ 3e5,1 ≤ S,T[i],C[i] ≤ 512。
由于N范围的剧增,我们要对上面解法二的转移方程进行优化。把带j的量看作自变量。
F[i] = min{F[j] - (sumT[i] + S) * sumC[j] + sumT[i] * sumC[i] + S * sumC[N]} j∈[0, i)
然后我们研究min内的式子。由于F[i]肯定由某个j得到,所以不妨去掉min。直接使:
F[i] = F[j] - (sumT[i] + S) * sumC[j] + sumT[i] * sumC[i] + S * sumC[N]。
然后移项:(以F[j]为纵坐标,sumC[j]为横坐标)
F[j] = (sumT[i] + S) * sumC[j] + F[i] - sumT[i] * sumC[i] - S * sumC[N]。
这是一条以sumT[i] + S为斜率,F[i] - sumT[i] * sumC[i] - S * sumC[N]为截距的直线。
所以我们的最优子选项就是一个坐标系下的点集。
除了F[i],斜率与截距都是定值。想使F[i]尽量小,则是让截距尽量小。
既然斜率固定,那么我们用一条斜率固定的直线从下往上平移,第一个碰到的就是最优解(此时截距最小)。
对于任意三个决策点(sumC[j1], F[j1]),(sumC[j2], F[j2]),(sumC[j3], F[j3]),不妨设 j1 < j2 < j3,因为T,C均为正整数(这点很重要),亦有sumC[j1] < sumC[j2] < sumC[j3]。
则,若j1,j2,j3构成上凸形状,j2一定不是最优决策。若构成下凸,则有可能是最优决策。
所以,j2成为最优决策的必要条件是:
(F[j2] - F[j1])/(sumC[j2] - sumC[j1]) < (F[j3] - F[j2])/(sumC[j3] - sumC[j2]) (即,一下凸壳)
通俗地讲,我们应该维护一个“连接相邻两点得到线段斜率单调递增”的下凸壳。
对于一个i,我们记k[i]为sumT[i] + S,则最优解应该取最小的那个j,j满足 k(j, j + 1) > k[i](画图就能得知了,如果k[i]再大一点,那么直接就可以够到靠后的那个点了)。
#include <cstdio> #define N 300010 typedef long long ll; int n, q[N]; ll s, sumt[N], sumc[N], f[N]; inline int read() { int x = 0; char c = getchar(); while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') {x = x * 10 + c - 48; c = getchar();} return x; } int main() { n = read(); s = read(); for(int i = 1; i <= n; ++i) { int t = read(), c = read(); sumt[i] = sumt[i - 1] + t; sumc[i] = sumc[i - 1] + c; } f[0] = 0; int l = 1, r = 1; q[1] = 0; for(int i = 1; i <= n; ++i) { while(l < r && (f[q[l + 1]] - f[q[l]]) <= (s + sumt[i]) * (sumc[q[l + 1]] - sumc[q[l]])) ++l; f[i] = f[q[l]] - (s + sumt[i]) * sumc[q[l]] + sumt[i] * sumc[i] + s * sumc[n]; while(l < r && (f[q[r]] - f[q[r - 1]]) * (sumc[i] - sumc[q[r]]) >= (f[i] - f[q[r]]) * (sumc[q[r]] - sumc[q[r - 1]])) --r; q[++r] = i; } printf("%lld", f[n]); return 0; }
Task 3(BZOJ2726)
数据范围:1 ≤ N ≤ 3e5,0 ≤ S,C[i] ≤ 512,-512 ≤ T[i] ≤ 512。
T[i]可以取负数,这意味着sumT不再具有单调性,从而,斜率S+sumT[i]不再具有单调性。我们就不能++l(删队头就是因为斜率的单调性,现在用不上,以后也用不上)了。所以维护整个凸壳(决策斜率该单增还是要单增的)。
那么,队头不一定是最佳决策,所以我们在队列中二分,求出一个位置p,使得k(p - 1, p) ≤ k[i] ≤ k(p, p + 1),p就是最优决策了。
#include <cstdio> #define N 300010 typedef long long ll; int n, q[N], l, r; ll s, sumt[N], sumc[N], f[N]; inline int read() { int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();} while(c >= '0' && c <= '9') {x = x * 10 + c - 48; c = getchar();} return x * f; } inline int binary_search(int k) { if(l == r) return q[l]; int L = l, R = r; while(L < R) { int mid = (L + R)>>1; if(f[q[mid + 1]] - f[q[mid]] <= k * (sumc[q[mid + 1]] - sumc[q[mid]])) L = mid + 1; else R = mid; } return q[L]; } int main() { n = read(); s = read(); for(int i = 1; i <= n; ++i) { int t = read(), c = read(); sumt[i] = sumt[i - 1] + t; sumc[i] = sumc[i - 1] + c; } f[0] = 0; l = 1, r = 1; q[1] = 0; for(int i = 1; i <= n; ++i) { // while(l < r && (f[q[l + 1]] - f[q[l]]) <= (s + sumt[i]) * (sumc[q[l + 1]] - sumc[q[l]])) ++l; int p = binary_search(s + sumt[i]); f[i] = f[p] - (s + sumt[i]) * sumc[p] + sumt[i] * sumc[i] + s * sumc[n]; while(l < r && (f[q[r]] - f[q[r - 1]]) * (sumc[i] - sumc[q[r]]) >= (f[i] - f[q[r]]) * (sumc[q[r]] - sumc[q[r - 1]])) --r; q[++r] = i; } printf("%lld", f[n]); return 0; }
Task 4
若T为正,C可能为负,如何处理?
Task 5
若T和C均可能为负,如何处理?
明显,利用平衡树动态插点就行,依旧维护整个凸壳,二分查找。
明天补T4解法与T4、T5代码。