hdu 3507 斜率dp

不好理解,先多做几个再看

此题是很基础的斜率DP的入门题。

题意很清楚,就是输出序列a[n],每连续输出的费用是连续输出的数字和的平方加上常数M

让我们求这个费用的最小值。

设dp[i]表示输出前i个的最小费用,那么有如下的DP方程:

dp[i]= min{ dp[j]+(sum[i]-sum[j])^2 +M }  0<j<i

其中 sum[i]表示数字的前i项和。

相信都能理解上面的方程。

直接求解上面的方程的话复杂度是O(n^2)

对于500000的规模显然是超时的。下面讲解下如何用斜率优化DP使得复杂度降低一维。

我们首先假设在算 dp[i]时,k<j ,j点比k点优。

也就是

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

所谓j比k优就是DP方程里面的值更小

对上述方程进行整理很容易得到:

[(dp[j]+sum[j]*sum[j])-(dp[k]+sum[k]*sum[k])] / 2(sum[j]-sum[k]) <=sum[i].

注意整理中要考虑下正负,涉及到不等号的方向。

左边我们发现如果令:yj=dp[j]+sum[j]*sum[j]   xj=2*sum[j]

那么就变成了斜率表达式:(yj-yk)/(xj-xk) <= sum[i];

而且不等式右边是递增的。

所以我们可以看出以下两点:我们令g[k,j]=(yj-yk)/(xj-xk)

第一:如果上面的不等式成立,那就说j比k优,而且随着i的增大上述不等式一定是成立的,也就是对i以后算DP值时,j都比k优。那么k就是可以淘汰的。

第二:如果 k<j<i   而且 g[k,j]>g[j,i] 那么 j 是可以淘汰的。

假设  g[j,i]<sum[i]就是i比j优,那么j没有存在的价值

相反如果 g[j,i]>sum[i] 那么同样有 g[k,j]>sum[i]  那么 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 b c,当i点要求解时,如果g[b,a]<sum[i],那么说明b点比a点更优,a点可以排除,于是a出队。最后dp[i]=getDp(q[head])。

 1 /*
 2 HDU 3507
 3 
 4 */
 5 
 6 #include<stdio.h>
 7 #include<iostream>
 8 #include<string.h>
 9 #include<queue>
10 using namespace std;
11 const int MAXN=500010;
12 
13 int dp[MAXN];
14 int q[MAXN];//队列
15 int sum[MAXN];
16 
17 int head,tail,n,m;
18 // dp[i]= min{ dp[j]+M+(sum[i]-sum[j])^2 };
19 int getDP(int i,int j)
20 {
21     return dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]);
22 }
23 
24 int getUP(int j,int k) //yj-yk部分
25 {
26     return dp[j]+sum[j]*sum[j]-(dp[k]+sum[k]*sum[k]);
27 }
28 int getDOWN(int j,int  k)
29 {
30     return 2*(sum[j]-sum[k]);
31 }
32 
33 int main()
34 {
35   //  freopen("in.txt","r",stdin);
36   //  freopen("out.txt","w",stdout);
37     while(scanf("%d%d",&n,&m)==2)
38     {
39         for(int i=1;i<=n;i++)
40            scanf("%d",&sum[i]);
41         sum[0]=dp[0]=0;
42         for(int i=1;i<=n;i++)
43            sum[i]+=sum[i-1];
44         head=tail=0;
45         q[tail++]=0;
46         for(int i=1;i<=n;i++)
47         {
48             //把斜率转成相乘,注意顺序,否则不等号方向会改变的
49             while(head+1<tail &&  getUP(q[head+1],q[head])<=sum[i]*getDOWN(q[head+1],q[head]))
50                head++;
51             dp[i]=getDP(i,q[head]);
52             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]))
53                     tail--;
54             q[tail++]=i;
55         }
56         printf("%d
",dp[n]);
57     }
58     return 0;
59 }
原文地址:https://www.cnblogs.com/cnblogs321114287/p/4319981.html