题意
一台机器有N个物品要处理,每个物品的处理时间是Ti,花费系数是Fi,可以把这N个物品分包处理,打包需要花费时间S,机器每处理完一包物品就会把当前时间显示出来(刚开始处理时时间为0),那么这包中每个物品的花费就是显示的这个时间乘以其花费系数。求处理完所有物品的最小花费。
分析
朴素的想法设状态为处理前i个物品的最小花费,但是单考虑前i个物品的花费是有后效性的,因为每新增一个包它的开始时间是由前面分组的组数决定的,所以必须再加一维状态来保存前面分的组数。这样考虑即使优化过后复杂度也至少为 O(N2)
换一种思路,既然前面的情况能够影响后面,那我们逆向DP,设状态dp[i]为第i个到第n个物品的最少花费。这样当前的决策影响的只是前面的结果,就不会有后效性。转移方程为:
dp[i]=mini≤j≤ndp[j]+(S+time[j]−time[i])∗sum[i])
其中time是花费时间的前缀和,sum[i]是代价系数的后缀和,斜率优化后可以在 O(N) 的时间求出答案
AC代码
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=1e4+100;
long long dp[maxn];
int N,S;
int times[maxn],sum[maxn];
int que[maxn],head,tail;
int getx(int j)
{
return -times[j-1];
}
long long gety(int j)
{
return dp[j];
}
int front(int i)
{
while(tail-head>=2&&
gety(que[head+1])-gety(que[head])<=
(getx(que[head+1])-getx(que[head]))*1LL*sum[i])
++head;
return que[head];
}
void insert(int i)
{
while((tail-head>=2)&&
(gety(que[tail-1])-gety(que[tail-2]))*(getx(i)-getx(que[tail-1]))>=
(gety(i)-gety(que[tail-1]))*(getx(que[tail-1])-getx(que[tail-2])))
--tail;
que[tail++]=i;
}
void init()
{
tail=head=0;
}
int main()
{
while(scanf("%d",&N)!=EOF)
{
scanf("%d",&S);
times[0]=sum[N+1]=0;
dp[N+1]=0;
init();
for(int i=1;i<=N;++i)
{
scanf("%d %d",times+i,sum+i);
times[i]+=times[i-1];
}
for(int i=N;i>=1;--i)
sum[i]+=sum[i+1];
insert(N+1);
for(int i=N;i>=1;--i)
{
int j=front(i);
dp[i]=dp[j]+(S+times[j-1]-times[i-1])*sum[i];
insert(i);
}
cout<<dp[1]<<endl;
}
return 0;
}