【题意】
N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务。 从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需 要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数Fi。请确定一个分组方案,使得总费用最小。(1 <= N <= 10000)
【题解】
S表示启动时间,T[i]是前i个任务的时间和,C[i]是前i个任务的开销和
f[i][j]=Min(f[i-1][k]+(S*i+T[j])*(c[j]-c[k]) ) ;
看了别人的结题报告,找到了优化到O(n*n)的方法。
就是从n往前推。
sumT[i]表示从i到n的任务所需要的时间总和, sumF[i]表示从i到n的费用系数总和,dp[i]表示对于从i到n的任务安排的最优解。
那么很容易可以得出这样一个简单的DP状态转移方程:(注: 数组存储从1到n)
从后往前推效率可以降一维的原因:
正向思考,在前面的分块情况不清楚的时候是没法决定下一块的开销的,但是反过来,假设前面都没有分块,先算后面的开销,然后,如果前面要分块,后面的开销就会全部多出来的一个S,将这个S算进当前的分块开销里面,于是倒过来动态成为可能。
单调队列斜率优化分析:
考虑dp[i],对i<j<k来说,如果保证决策k不比决策j差的条件是:
dp[j]+(s+sumt[i]-sumt[j])*sumf[i]>=dp[k]+(s+sumt[i]-sumt[j])*sumf[i];
整理得:
(dp[k]-dp[j])/(sumt[k]-sumt[j])>=sumf[i];
设:
b[i,x]=dp[x]+(s+sumt[i]-sumt[x])*sumf[i];
g[k,j]=(dp[k]-dp[j])/(sumt[k]-sumt[j])
由上面的式子可知:
b[i,j]>=b[i,k] <==> g[k,j]>=sumf[i]; 决策k不比决策j差
b[i,j]<b[i,k] <==> g[k,j]<sumf[i]; 决策j比决策k 优
进而可知:
当i<c<b<a时,如果有g[a,b]>g[b,c],那么b永远不会成为dp[i]的决策。
证明:
如果g[a, b] >
(1)如果g[a, b] >= sumF[i],那么决策a不会比决策b差,也就说决策b不可能是决策点
(2)如果g[a, b] < sumF[i],那么由于g[a, b] > g[b, c],那么g[b, c] < sumF[i],那么决策c要比决策b好,所以b还不能作为决策点
根据上面的结论和一些特性,我们可以考虑维护一个斜率的队列来优化整个DP过程:
(1)假设a, b, c依次是队列尾部的元素,那么我们就要考虑g[a, b]是否大于g[b, c],如果g[a, b] > g[b, c],那么可以肯定b一定不会是决策点,所以我们可以从队列中将b去掉,然后依次向前推,直到找到一个队列元素少于3个或者g[a, b] <= g[b, c]的点才停止。
(2)假设a, b是依次是队列头部的元素,那么我们知道,如果g[a, b] < sumF[i]的话,那么对于i来说决策点b肯定优于决策点a,又由于sumF[i]是随着i减少而递增的(这个就是为什么倒推的原因),所以当g[a, b] < sumF[i]时,就一定有g[a, b] < sumF[i-1],因此当前的决策点a不仅仅在考虑dp[i]时不会是最佳决策点,而且在后面的DP中也一定不会是最佳决策点,所以我们可以把a从队列的头部删除,依次往后如此操作,直到队列元素小于2或者g[a, b] >= sumF[i]。
(3)对于i的更新,一定是队列头部的决策点最好,所以O(1)即可转移。
【代码】
#include <iostream>
using namespace std;
const int maxn=10005;
long long dp[maxn];
int st[maxn],sf[maxn],t[maxn],f[maxn],q[maxn];
int n,s;
double cal(int x,int y)
{
return double(dp[x]-dp[y])/double(st[x]-st[y]);
}
int main()
{
freopen("pin.txt","r",stdin);
freopen("pou.txt","w",stdout);
int i,h,r,j;
scanf("%d",&n);
scanf("%d",&s);
for (i=1;i<=n;i++)
scanf("%d%d",&t[i],&f[i]);
for (i=n;i;i--)
{
st[i]=st[i+1]+t[i];
sf[i]=sf[i+1]+f[i];
}
h=1;r=0;
dp[n]=(s+st[n])*sf[n];
q[++r]=n;
long long tt;
for (i=n-1;i;i--)
{
while (r-h+1>=2 && cal(q[h],q[h+1])<sf[i]) h++;
tt=s+st[i];
tt*=sf[i];
dp[i]=tt;
j=q[h];
tt=s+st[i]-st[j];
tt*=sf[i];
dp[i]=min(dp[i],dp[j]+tt);
while (r-h>=1 && cal(q[r-1],q[r])>cal(q[r],i)) r--;
q[++r]=i;
}
cout << dp[1] << endl;
return 0;
}