P2365任务分配(斜率优化+思维+DP)
题意:
N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务。从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数Fi。请确定一个分组方案,使得总费用最小。
例如:S=1;T={1,3,4,2,1};F={3,2,3,3,4}。如果分组方案是{1,2}、{3}、{4,5},则完成时间分别为{5,5,10,14,14},费用C={15,10,30,42,56},总费用就是153。
题解:斜率优化+思维+DP
A:开始的时候我直接上来拍了一个二维DP[I][J],代表前I个分成J段的花费,然后状态转移方程
DP[I][J] = MIN(DP[I][J], DP[K][J-1] + (SJ+T[I])(C[I]-C[J]),K代表前K个,C和T分别代表了花费和时间的前缀和,这个式子很好推,但是会T飞起来。也不好优化。O(N^3)。
B:后来一想,这个没有要求分成几段,也就是可以把J给压缩掉,用DP[I]来表示前I个可以达到的最小费用。状态方程变成
DP[I] = MIN(DP[I], DP[J] + T[I] * (C[I]-C[J]) + S*[C[N] - C[J]])。这里和上面不同的就是S*[C[N] - C[J]],这里用了一个逆向思维,因为前面有几段没有办法计算,我们就去想后面的会被怎么影响,那么后面的S的影响就是当前的S乘上后面所有的费用。这样就OK了。即:费用提前的思想。注意要预处理了一下,I之前所有的物品分成一段的情况。然后这题目O(N^2)就可以过了,但是某某大佬突然对我这个蒟蒻说了一句,这题O(N)就可以过,于是就有了第三种解法。
C:斜率优化。
首先我们先化简B的状态转移方程:DP[I] = MIN(DP[I], DP[J] + T[I] * (C[I]-C[J]) + S*[C[N] - C[J]])。化简得到:
DP[J] = C[J](T[I]+S)+DP[I]-T[I]C[I]-SC[N]; 把这个方程想象为f(x) = kx+b。那么f(x)就是DP[J]。k是(T[I]+S)。x是C[J],b就是DP[I]-T[I]C[I]-SC[N]。那么这样的方程就可画出一条折线来,因为我们要DP[I]最小,那就是b最小,所以我们要找所有折点中能让b最小的点。这里可以用手动模拟一下。
可以想象一下以上图片。由图片我们可以知道:会让目标线B最小的肯在J2,而J2的斜率也是正好在大雨目标线的第一个斜率。即下凸的最优解。当然也有不能的情况,比如以下:
在这里中J2就是不满足的,这里J1的斜率就认为还是小于目标线的,但这样上凸的情况我们要舍弃,所以我们直接吧J2点忽略,链接J1和J3,以此类推其他情况。最后我们用一个优先队列来维护折点,把凸包中小于的都舍弃掉,直到最优解,后面的上凸的情况就直接把大于的删除,然后把J3压进去。然后就是方程直接写,一直递推。
代码B:O(N^2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mx = 3e5 + 7;
const ll INF = 8e9 + 7;
struct node {
ll a, b;
} z[mx];
ll pret[mx],prec[mx];
ll dp[mx];
int main() {
ios_base::sync_with_stdio(false);
#ifdef ACM_LOCAL
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
int s,n;
cin>>n>>s;
pret[0] = prec[0] = 0;
for (int i=1;i<=n;i++) {
cin >> z[i].a >> z[i].b;
pret[i] += pret[i-1]+z[i].a;
prec[i] += prec[i-1]+z[i].b;
dp[i] = INF;
}
for (int i=1;i<=n;i++)
dp[i] = pret[i] * (prec[i]) + s * (prec[n]); //考虑只有N个元素只分一段的情况
for (int i=1;i<=n;i++) {
for (int j = 1; j < i; j++)
dp[i] = min(dp[i], dp[j] + pret[i] * (prec[i] - prec[j]) + s * (prec[n] - prec[j]));
}
cout<<dp[n]<<endl;
return 0;
}
代码C:O(N)斜率优化
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mx = 3e5 + 7;
const ll INF = 8e9 + 7;
struct node {
ll a, b;
} z[mx];
ll pret[mx],prec[mx];
ll dp[mx];
ll q[mx];
int main() {
ios_base::sync_with_stdio(false);
#ifdef ACM_LOCAL
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
int s,n;
cin>>n>>s;
pret[0] = prec[0] = 0;
for (int i=1;i<=n;i++) {
cin >> z[i].a >> z[i].b;
pret[i] += pret[i-1]+z[i].a;
prec[i] += prec[i-1]+z[i].b;
dp[i] = INF*INF;
}
dp[0] = 0;
int l =1,r = 1;
for (int i=1;i<=n;i++){
while (l<r && (dp[q[l+1]]-dp[q[l]])<=(s+pret[i])*(prec[q[l+1]]-prec[q[l]])) l++;
dp[i] = dp[q[l]]+pret[i]*prec[i] + s*prec[n] - prec[q[l]]*(s+pret[i]);
while (l<r && (dp[i] - dp[q[r]])*(prec[q[r]]-prec[q[r-1]])<= (dp[q[r]] -dp[q[r-1]])*(prec[i] - prec[q[r]])) r--;
q[++r] = i;
}
cout<<dp[n]<<endl;
return 0;
}