HDU3507 Print Article【斜率优化DP】

博客围绕HDU相关问题展开,给定n个数和常数m,将数分组,每组花费是组内数和的平方加m,求最小花费。先给出朴素DP方程,但因n范围大易超时,进而采用斜率优化,介绍了用单调队列维护解集、求解及维护下凸包的方法,还探讨了维护凸包时的特殊情况。

Problem Description

Zero has an old printer that doesn't work well sometimes. As it is antique, he still like to use it to print articles. But it is too old to work for a long time and it will certainly wear and tear, so Zero use a cost to evaluate this degree.
One day Zero want to print an article which has N words, and each word i has a cost Ci to be printed. Also, Zero know that print k words in one line will cost


M is a const number.
Now Zero want to know the minimum cost in order to arrange the article perfectly.

Input

There are many test cases. For each test case, There are two numbers N and M in the first line (0 ≤ n ≤ 500000, 0 ≤ M ≤ 1000). Then, there are N numbers in the next 2 to N + 1 lines. Input are terminated by EOF.

Output

A single number, meaning the mininum cost to print the article.

Sample Input

5  5

5

9

5

7

5

Sample Output

230

题意:

有n个数,常数为m,将这n个数分组,每一组的花费是这一组内所有数的和的平方再加上常数m,问分组的最小花费

分析:

只求最终结果而不要求如何分组,显然可以联系到DP,那么就可以写出最朴素的DP方程:

dp[i]=min(dp[j]+(sum[i]-sum[j])^2+m)

这是个O(n^2)的DP,显然n这个5e5的范围会超时,然后就想到要斜率优化

k<j,且dp[j]+(sum[i]-sum[j])^2+m<dp[k]+(sum[i]-sum[k])^2+m

显然此时j优于k

乘开移项合并同类项后可以得到 

\frac{(dp[j]+sum[j]^2)-(dp[k]+sum[k]^2)}{2(sum[j]-sum[k])}<sum[i]

令 Y(x)=dp[x]+sum[x]^2,X(x)=sum[x]

那么上面的式子就可以化成

 \frac{Y[j]-Y[k]}{2(X[j]-X[k])}<sum[i]

再令 g[j,k]=\frac{Y[j]-Y[k]}{2(X[j]-X[k])}<sum[i]

所以若满足 g[j,k]<sum[i] 就表示j优于k

 

得到这样一个斜率表达式后就可以进行斜率优化了:

设 k<j<i 

如果g[i,j]<g[j,k]<sum[i]   那么i优于j优于k,i为答案

如果g[i,j]>g[j,k]>sum[i]   那么k优于j优于i,k为答案

这两种情况下j都不会是答案,那么就不需要进行答案的转移判断,这就是一种优化

即就是:按照斜率(g[x,y])的递增维护一个下凸包 就可以在O(n)实现

 

做法总结如下:

1、核心:用一个单调队列维护解集;

2、求解:从队头开始,如果已有元素a b c,当i点要求解时,如果g[b,a]<sum[i],那么说明b点比a点更优,a点可以排除,于是a出队。最后dp[i]=getDp(q[head])

3、维护:假设队列中从头到尾已经有元素a b c。那么当d要入队的时候,我们维护队列的下凸性质,即如果g[d,c]<g[c,b],那么就将c点删除。直到找到g[d,x]>g[x,y]为止,并将d点加入在该位置中。

参考博客:Accept的博客

【但我觉得这篇博客里队列维护的单调性那里好像有一点点问题嗷qwq】

【我还看到12年kuangbin的评论qwq】

 

一点问题:维护凸包那里如果 当前的点和队尾的斜率 与 队尾两点的斜率 相等的话,我觉得放不放进队列里都没有影响的吧

然后我写完这句话就发现其实是不对的qwq

于是我又去问了11-大佬嘤嘤嘤

假如现在队列里已经有了a b c,现在要看d能不能入,这时候 g[b,c]==g[c,d]

这种时候d就要入队列然后弹出c,

如果d进但不弹出c 那么更新head的时候就会丢失b的信息

如果d不进也不弹出c 那么继续向后更新凸包时就丢失了d的信息

所以这种情况的正确理解是,虽然当前都是等效的,但是还是要继续向后更新,所以要入d弹出c

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+7;
int dp[maxn],sum[maxn],q[maxn];
//dp[i]=min{dp[j]+M+(sum[i+1]-sum[j])^2};
int head,tail,n,m;
int getdp(int i,int j) {return dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]);}
int getup(int j,int k) {return dp[j]+sum[j]*sum[j]-(dp[k]+sum[k]*sum[k]);}
int getdown(int j,int k) {return 2*(sum[j]-sum[k]);}
int main()
{
	while (~scanf("%d%d",&n,&m))
	{
		for (int i=1;i<=n;i++) scanf("%d",&sum[i]);
		sum[0]=0;
		for (int i=1;i<=n;i++) sum[i]+=sum[i-1];
		head=tail=0;
		q[tail++]=0;
		dp[0]=0;
		for (int i=1;i<=n;i++)
		{
			while (head+1<tail && getup(q[head+1],q[head])<=sum[i]*getdown(q[head+1],q[head])) head++;
			dp[i]=getdp(i,q[head]);
			while (head+1<tail && getup(i,q[tail-1])*getdown(q[tail-1],q[tail-2])<=getup(q[tail-1],q[tail-2])*getdown(i,q[tail-1])) tail--;
			q[tail++]=i;
		}
		printf("%d\n",dp[n]);
	}
	return 0;
}

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值