批任务调度:N个顺序任务,分别耗时Ti,权重Fi。 若批处理,每批任务耗时S+Ti之和,同批次任务视作同时完成。总耗时等于每个任务完成的时刻乘以其权重,求最少耗时?
4.4常用技巧精选(二)
双端队列
最朴素的思路就是dp了,定义
dp[i] := 从任务i到N的最少耗时
从后往前递推,有状态转移方程
dp[j] = min(dp[j], t * f + dp[j + i]);
其中f是从j到N的F之和,相对于i来讲是个常量。t是从j到N的T之和,随着i增加。
看起来是个O(n2)的糟糕算法,也的确会TLE。
但擦亮眼睛,剑走偏锋,题目的数据范围给的很清楚
1 <= N <= 10000 0 <= S <= 50 1 <= Ti <= 100
S最多相当于一个批次执行时间的1/200,暗示着一个批次不会超过200个任务。于是限制一下搜索分支,就水过去了。
#include <iostream> using namespace std; const int MAX_N = 10000 + 8; int S, N; int T[MAX_N], F[MAX_N]; int sum_F[MAX_N]; int dp[MAX_N]; int solve() { for (int j = N - 1; j >= 0; --j) { int f = sum_F[N] - sum_F[j], t = S; for (int i = 1; j + i <= N && i <= 200; i++) { t += T[j + i - 1]; dp[j] = min(dp[j], t * f + dp[j + i]); } } return dp[0]; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); #endif scanf("%d%d", &N, &S); memset(dp, 0x3f, N * sizeof(int)); for (int i = 0; i < N; ++i) { scanf("%d%d", T + i, F + i); sum_F[i + 1] = sum_F[i] + F[i]; } printf("%d\n", solve()); #ifndef ONLINE_JUDGE fclose(stdin); #endif return 0; }
16491862 | hankcs | 1180 | Accepted | 320K | 63MS | C++ | 776B | 2017-01-18 11:37:56 |
想走正道的话,老老实实地用双端队列吧。
定义
dp[i] := 从 1 ~ i 的最小耗时贡献
这里贡献的意思是,假设任务i到N存在但是T=0,任务1到i的最小耗时。比如说假设一共2个任务,每个任务独占一个批次
dp[1] = (f1+f2)*t1+S*(f1+f2) dp[2] = min{dp[1] + f2*(t1+t2), dp[1] + f2*t2} = dp[1] + f2*t2
这个min的意思是,有两个分支,前者将两个任务放到一个批次中,后者放到两个批次中。
虽然看起来没什么规律,但我们稍微变一下形式
sum_F[i] := F的前i项之和,同理定义sum_T dp[0] = 0 dp[1] = (sum_F[N)-sum_F[0])*sum_T[1]+dp[0]+(S-sum_T[0])*(sum_F[N]-sum_F[0]) dp[2] = (sum_F[N)-sum_F[1])*sum_T[2]+dp[1]+(S-sum_T[1])*(sum_F[N]-sum_F[1])
就能抽象出一条直线来:
dp[i] = min{k[1~i]*sum_T[1~i]+b[1~i]} k[i] = sum_F[N] - sum_F[i-1] b[i] = dp[i-1]+(S-sum_T[i-1])*(sum_F[N]-sum_F[i-1])
其实蛮好理解的,假设dp[i]->dp[j],那么k[i]*sum_T[1~i]表示1~i-1的T都会累积到i~N的任务上去,这还不够,还需要加上b[i]。b[i]中的dp[i-1]是前i-1个任务的最小耗时贡献,后面是一个对贡献的修正。
上面的dp方程跟书上是类似的
dp[i]=S[i]+min{fj(i)|0≤j≤i-k}
其中f是关于dp的直线方程,直接利用书上的结论就行了。
#include <iostream> #include <deque> using namespace std; // (k, b) := 直线kx+b struct Line { int k, b; Line(int k, int b) : k(k), b(b) {} }; const int MAX_N = 10000 + 8; int N; int S; int sum_T[MAX_N]; int sum_F[MAX_N]; int dp[MAX_N]; // dp[i] := 从 1 ~ i 的最小耗时贡献 int main() { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); #endif scanf("%d%d", &N, &S); for (int i = 1; i <= N; i++) { scanf("%d%d", sum_T + i, sum_F + i); sum_T[i] += sum_T[i - 1]; sum_F[i] += sum_F[i - 1]; } deque<Line> lines; lines.push_back(Line(sum_F[N], S * sum_F[N])); for (int i = 1; i <= N; i++) { int k1, b1; int k2, b2; int k3, b3; while (lines.size() > 1) { k1 = lines[0].k; b1 = lines[0].b; k2 = lines[1].k; b2 = lines[1].b; if (k1 * sum_T[i] + b1 >= k2 * sum_T[i] + b2) lines.pop_front(); // 若头部的值不是最小值了则删去 else break; } k1 = lines[0].k; b1 = lines[0].b; dp[i] = k1 * sum_T[i] + b1; k3 = sum_F[N] - sum_F[i]; b3 = dp[i] + (S - sum_T[i]) * (sum_F[N] - sum_F[i]); while (lines.size() > 1) { k1 = lines[lines.size() - 2].k; b1 = lines[lines.size() - 2].b; k2 = lines[lines.size() - 1].k; b2 = lines[lines.size() - 1].b; if ((k2 - k1) * (b3 - b2) >= (b2 - b1) * (k3 - k2)) // tail不可能成为最小值的直线 lines.pop_back(); else break; } lines.push_back(Line(k3, b3)); } printf("%d\n", dp[N]); #ifndef ONLINE_JUDGE fclose(stdin); #endif return 0; }
16492080 | hankcs | 1180 | Accepted | 296K | 63MS | C++ | 1727B | 2017-01-18 12:47:35 |
虽然从运行时间上来看并无卵用。
知识共享署名-非商业性使用-相同方式共享:码农场 » POJ 1180 Batch Scheduling 题解《挑战程序设计竞赛》