hdu-2829 Lawrence[斜率dp]

参考了kuangbin大神的。

题意:大概就是给你n(1<=n<=1000)个数,
要你将其分成m + 1(0<=m<n)组,
要求每组数必须是连续的而且要求得到的价值最小。
一组数的价值定义为该组内任意两个数乘积之和,
如果某组中仅有一个数,那么该组数的价值为0。如:
将“4 5 1 2”分成一组得到的价值为:
4*5 + 4*1 + 4*2 + 5*1 + 5*2 + 1*2 = 49;
将“4 5 1 2”分成“4 5”和“1 2”两组得到的价值为:
4*5 + 1*2 = 22;
将“4 5 1 2”分成“4”和“5 1 2”两组得到的价值为:
0 + 5*1 + 5*2 + 1*2 = 17。

f[i][j]表示把第1~j元素分为i组两两乘积的最大

w[i]表示第i个元素的值

Sw[i]表示第1~i元素之和

Tw[i]表示第1~i元素的两两乘积

Sw[i]=Sw[i-1]+w[i];

Tw[i]=Tw[i-1]+Sw[i]*Sw[i-1];

f[i][j]=max(f[i-1][k]+cost(k+1,j))  i<=k<j

这种形式的dp方程可以考虑用斜率优化,需要验证其单调性

f[i][j]=max(f[i-1][k]+Tw[j]-Tw[k]-Sw[k]]*(Sw[j]-Sw[k]);

f[i][j]=max(f[i-1][k]+Tw[j]-Tw[k]-Sw[k]*Sw[j]-Sw[k]*Sw[k]);

令k1<k2;G(i,j,k)表示f[i][j]在枚举k状态时的检测值

G(i,j,k1)=f[i-1][k1]+Tw[j]-Tw[k1]-Sw[k1]*Sw[j]-Sw[k1]*Sw[k1]);

G(i,j,k2)=f[i-1][k2]+Tw[j]-Tw[k2]-Sw[k2]*Sw[j]-Sw[k2]*Sw[k2]);

G(i,j,k1)-G(i,j,k2)=f[i-1][k1]-T[k1]-Sw[k1]*Sw[j]+Sw[k1]*Sw[k1]-(f[i-1][k2]-T[k2]-Sw[k2]*Sw[j]+Sw[k2]*Sw[k2]);

打表或者其它方法可知G(i,j,k2)-G(i,j,k2)<0,

则 

这说明G(i,j,k1)-G(i,j,k2)满足slope(k1,k2)>Sw[j],slope(p,q)表示(y[p]-y[q])/(x[p]-x[q]),在这里 x=Sw,y=f[]-Tw+Sw^2,slope(k1,k2)>Sw[j].意思就是对于f[i][j]的每个检测点两两斜率是一定大于Sw[j]。

G满足这样的规律有啥用呐?

设k1表示最优决策点,可知 slope(1~k1-1,k1),slope(k,k+1~j)>=Sw[j].

设k2表示对于f[i][j+1]的最优解。有slope(1~k2-1,k2),slope(k2,k2+1~j+1)>=Sw[j+1],而由条件给出的Sw[j+1]>Sw[j]。那么slope(k1,k2)>Sw[j+1]。

若再引入k3为f[i][j+2]的最优解。同理slope(k2,k3)>Sw[j+2]。则slope(k3,k2)>slope(k2,k1)

这样可以推出:决策点 f[i][j1],f[i][j2],f[i][j3],f[i][j4],j1<j2<j3<j4 他们的最优决策点k1,k2,k3,k4 满足slope(k1,k2)<slope(kl2,k3)<slope(k3,k4)



这样就可以对j这以一维度建立凸包,然后维持一个单调队列,使队列里,相邻两两间斜率递增,每个下凸性决策在进行第j决策时使用后即可将其与其之前的决策点抛弃。

所以每个元素的之只出入队列一次.时间效率从O(n)优化到O(1)。

/*题意:大概就是给你n(1<=n<=1000)个数,
要你将其分成m + 1(0<=m<n)组,
要求每组数必须是连续的而且要求得到的价值最小。
一组数的价值定义为该组内任意两个数乘积之和,
如果某组中仅有一个数,那么该组数的价值为0。如:

将“4 5 1 2”分成一组得到的价值为:
4*5 + 4*1 + 4*2 + 5*1 + 5*2 + 1*2 = 49;

将“4 5 1 2”分成“4 5”和“1 2”两组得到的价值为:
4*5 + 1*2 = 22;

将“4 5 1 2”分成“4”和“5 1 2”两组得到的价值为:
0 + 5*1 + 5*2 + 1*2 = 17。
*/
#include <stdio.h>
using namespace std;
const int maxn=1008;
int Sw[maxn],Tw[maxn],w[maxn];
int f[maxn][maxn];
int que[maxn],head,tail;
int n,m;
int dy(int i,int x2,int x1)
{
	return f[i][x1]-Tw[x1]+Sw[x1]*Sw[x1]-(f[i][x2]-Tw[x2]+Sw[x2]*Sw[x2]);
}
int dx(int x2,int x1)
{
	 return Sw[x1] - Sw[x2];   
}
void DP() 
{
	for(int i=1;i<=n;++i)
		f[1][i]=Tw[i];
	for(int i=2;i<=m+1;++i)
	{
		head=tail=0;
		que[tail++]=i-1;
		for(int j=i;j<=n;++j)
		{
			while(tail-head>=2)
			{
				int p=que[head],q=que[head+1];
				if(dy(i-1,p,q)<Sw[j]*dx(p,q)) ++head;
				else break;
			}
			f[i][j]=f[i-1][que[head]]+Tw[j]-Tw[que[head]]-Sw[que[head]]*(Sw[j]-Sw[que[head]]);
			while (tail - head >= 2)
			{  
                int p = que[tail-2], q = que[tail-1];
				if(dy(i-1,p,q)*dx(q,j)>=dx(p,q)*dy(i-1,q,j)) --tail;
				else break; 
            }
            que[tail++]=j;
		}
	} 
	printf("%d\n",f[m+1][n]);
}
int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		if(n==0&&m==0) break;
		Sw[0]=Tw[0]=0;
		for(int i=1;i<=n;++i)
		{
			scanf("%d",w+i);
			Sw[i]=w[i]+Sw[i-1];
			Tw[i]=Tw[i-1]+w[i]*Sw[i-1];
		}
		DP();
	}
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值