思路:倒序进行DP,dp(i) 为完成 i 后任务的最小花费(从 i 开始做),令 cost=sumt(j−1)−sumt(i−1)+s(i∼j−1 所用时间) 可以得到状态转移方程:
dp(i)=mindp(j)+cost⋅(sumf[j−1]−sumf[i−1])+(sumf(n)−sumf(j−1))(j∈(i,n])
其中 sumt 和 sumf 分别是 t 和 f 的前缀和数组。
化简一下可以得到:
dp(i)=mincost⋅(sumf(n)−sumf(i−1))
假设 j<k 且 j 是比 k 更优的决策点,推推式子就可以得到:
sumf(i−1)≤sumt(j−1)−sumt(k−1)dp(j)+sumt(j−1)⋅sumf(n)−dp(k)+sumt(k−1)⋅sumf(n))
所以用单调队列维护,保证下标递减,斜率递减。
WA on test 10,不保证式子一定正确。
代码如下:
#include<bits/stdc++.h>
#define ll long long
#define db double
using namespace std;
inline int read()
{
int x = 0; bool op = 0;
char c = getchar();
while(!isdigit(c))op |= (c == '-'), c = getchar() ;
while(isdigit(c))x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return op ? -x : x;
}
const int N = 5010;
int n, s;
int sumt[N], sumf[N], dp[N];
deque<int> q;
int top(int x, int y)
{
return dp[x] + sumt[x - 1] * sumf[n] - dp[y] - sumt[y - 1] * sumf[n];
}
int down(int x, int y)
{
return sumt[x - 1] - sumt[y - 1];
}
int main()
{
n = read(); s = read();
for(int i = 1; i <= n; i++)
{
sumt[i] = read(); sumf[i] = read();
sumt[i] += sumt[i - 1]; sumf[i] += sumf[i - 1];
}
q.push_back(n + 1);
for(int i = n; i >= 1; i--)
{
while(q.size() > 1 && top(q[1], q[0]) <= sumf[i - 1] * down(q[1], q[0]))
q.pop_front();
int j = q.front();
int cost = sumt[j - 1] - sumt[i - 1] + s;
dp[i] = dp[j] + cost * (sumf[n] - sumf[i - 1]);
int s = q.size() - 1;
while(q.size() > 1 && top(i, q[s]) * down(q[s], q[s - 1]) >= top(q[s], q[s - 1]) * down(i, q[s]))
{
q.pop_back();
s = q.size() - 1;
}
q.push_back(i);
}
printf("%d", dp[1]);
return 0;
}