题意:
现有n个产品,按初始顺序,每次可以将一个产品入栈,或将栈顶产品弹至现在的序列末尾.每个产品有一个制作时间(t_i)和单位时间惩罚值(d_i),总的惩罚值为(sum_{i=1}^{n})((s_i×d_i)),其中(s_i)为第i个产品的完成时间,你需要最小化总的惩罚值.
分析:
考虑最后出栈的是 i,则 1 至 i-1 在 i 入栈前就已经弹出,与 i+1 至 n 的顺序没有关系,并且 i+1 至 n 的惩罚值只跟他们的顺序与(sum{t_j}) ((1<=j<i))有关,即可以将[1,n]的计算转化为两个子问题[1,i-1]和[i+1,n]的计算.
令(f_{l,r})表示([l,r])的最小惩罚值.
(f_{l,r}= min(f_{l,mid-1}+f_{mid+1,r} +(st_{ mid-1}-st_{l-1})*(sd_r-sd_{mid})+(st_r-st_{l-1} )*d_{mid})) ((l<=mid<=r),其中 st 为时间前缀和,sd 为惩罚前缀和)
详解这个转移方程式:(理解了这个题目就做完了)
这里我们假设区间([l,r])中最后出栈的是mid,所以lmid-1一定是在mid入栈前就出栈了(这里抓住栈的特性来理解),而mid+1r就更显然是在mid之后才进栈(这里是因为进栈顺序固定是lmidr).
所以区间([mid+1,r])中的元素所产生的贡献是区间([l,mid-1])元素的总用时((st_{ mid-1}-st_{l-1}))和自己区间的惩罚前缀和((sd_r-sd_{mid}))的乘积.
最后还要加上最后出栈的mid所产生的贡献,即整个区间的总用时((st_r-st_{l-1}))与它自己的惩罚((d_{mid}))的乘积
时间复杂度 (O(N^3)).空间复杂度 (O(N^2)).
int n;
int t[205],d[205],st[205],sd[205];
long long f[205][205];
int main(){
n=read();
for(int i=1;i<=n;i++){
t[i]=read();//时间
d[i]=read();//惩罚
st[i]=st[i-1]+t[i];//时间前缀和
sd[i]=sd[i-1]+d[i];//惩罚前缀和
}
memset(f,0x3f,sizeof(f));
//因为题目是要求惩罚最小值,所以f数组要赋最大值
for(int i=1;i<=n;i++){
f[i][i]=t[i]*d[i];
//初始化:i单独构成一个区间 所产生的惩罚
f[i][i-1]=0;
//[i,i-1]是非法区间,所以惩罚为零
}
f[n+1][n]=0;
//[n+1,n]也是非法区间,惩罚为零
//之所以要把以上两个非法区间赋值为零,是因为我们之后的
//状态转移会出现这两种情况,如果它们是最大值,影响答案
for(int l=1;l<n;l++)//枚举区间长度
for(int i=1;i+l<=n;i++){//枚举区间左端点
int j=i+l;//根据区间长度和左端点表示右端点
for(int k=i;k<=j;k++)//枚举区间中的点
f[i][j]=min(f[i][j],f[i][k-1]+f[k+1][j]+1LL*(st[k-1]-st[i-1])*(sd[j]-sd[k])+1LL*(st[j]-st[i-1])*d[k]);
}
printf("%lld
",f[1][n]);
return 0;
}