[2015北大自招夏令营]产品排序(区间DP)

题意:

现有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\n",f[1][n]);
    return 0;
}

posted on 2019-01-27 21:37  PPXppx  阅读(107)  评论(0编辑  收藏  举报