您的位置:首页 > 其它

hdu 3507 Print Article(斜率dp)

2016-05-05 21:19 127 查看
题意:一个打印机要输出N个数字a
,输出的时候可以连续连续的输出,每连续输出一串,它的费用是 “这串数字和的平方加上一个常数M”。



思路:我们设dp[i]表示输出到i的时候最少的花费,sum[i]表示从a[1]到a[i]的数字和。于是方程就是:

dp[i]=dp[j]+M+(sum[i]-sum[j])^2;

很显然这个是一个二维的。题目的数字有500000个,不用试了,二维铁定超时了。那我们就来试试斜率优化吧,看看是如何做到从O(n^2)复杂度降到O(n)的。

分析:

我们假设k<j<i。如果在j的时候决策要比在k的时候决策好,那么也是就是dp[j]+M+(sum[i]-sum[j])^2<dp[k]+M+(sum[i]-sum[k])^2。(因为是最小花费嘛,所以优就是小于)

两边移项一下,得到:(dp[j]+num[j]^2-(dp[k]+num[k]^2))/(2*(num[j]-num[k]))<sum[i]。我们把dp[j]-num[j]^2看做是yj,把2*num[j]看成是xj。

那么不就是yj-yk/xj-xk<sum[i]么? 左边是不是斜率的表示?

那么yj-yk/xj-xk<sum[i]说明了什么呢? 我们前面是不是假设j的决策比k的决策要好才得到这个表示的?如果是的话,那么就说明g[j,k]=yj-jk/xj-xk<sum[i]代表这j的决策比k的决策要更优。

关键的来了:现在从左到右,还是设k<j<i,如果g[i,j]<g[j,k],那么j点便永远不可能成为最优解,可以直接将它踢出我们的最优解集。为什么呢?

我们假设g[i,j]<sum[i],那么就是说i点要比j点优,排除j点。

如果g[i,j]>=sum[i],那么j点此时是比i点要更优,但是同时g[j,k]>g[i,j]>sum[i]。这说明还有k点会比j点更优,同样排除j点。

排除多余的点,这便是一种优化!

接下来看看如何找最优解。

设k<j<i。

由于我们排除了g[i,j]<g[j,k]的情况,所以整个有效点集呈现一种上凸性质,即k j的斜率要大于j i的斜率。



这样,从左到右,斜率之间就是单调递减的了。当我们的最优解取得在j点的时候,那么k点不可能再取得比j点更优的解了,于是k点也可以排除。换句话说,j点之前的点全部不可能再比j点更优了,可以全部从解集中排除。

于是对于这题我们对于斜率优化做法可以总结如下:

1,用一个单调队列来维护解集。

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

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

参考链接:http://blog.csdn.net/azheng51714/article/details/8214165

/*
我们 dp[i]=dp[j]+(sum[i]-sum[j])^2+M;
那么 如果对于 j<k<i 有 dp[k]+(sum[i]-sum[k])^2+M<dp[j]+(sum[i]-sum[j])^2+M;
整理得到:dp[k]+sum[k]^2-(dp[j]+sum[j])<2*sum[i]*(sum[k]-sum[j]);
我们另 dy(i)=dp[i]+sum[i]^2,a=2*sum[i],dx(k,j)=(sum[k]-sum[j]);
则 原表达式表示为:dy(k)<a*dx(k,j)+dy(j);
我们要找最大值 只需找到 该直线向上平移的直线与数据的第一个交点即可。。
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=1<<29;
const int maxn=500002;
int n,m,a[maxn],q[maxn];
__int64 sum[maxn],dp[maxn];
__int64 dy(int i)
{
return dp[i]+sum[i]*sum[i];
}
__int64 dx(int j,int k)
{
return 2*(sum[j]-sum[k]);
}
__int64 f(int j,int k)
{
return ((dy(j)-dy(k)));
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
int i,j,head=0,tail=-1;
sum[0]=0;
for(i=1; i<=n; i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
q[++tail]=0;//因为没有这一步,导致WA了n次。。。。
for(i=1; i<=n; i++)
{
while(head<tail&&f(q[head+1],q[head])<=sum[i]*dx(q[head+1],q[head]))++head;
dp[i]=dp[q[head]]+(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]])+m;
while(head<tail&&f(i,q[tail])*dx(q[tail],q[tail-1])<=f(q[tail],q[tail-1])*dx(i,q[tail]))--tail;
q[++tail]=i;
}
printf("%I64d\n",dp
);
}
return 0;
}
/*
5 5
5
9
5
7
5
*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: