HDOJ 3507 Print Article (斜率优化DP)

来源:互联网 发布:csol2控制台fps优化 编辑:程序博客网 时间:2024/05/20 21:43

Print Article

Time Limit: 9000/3000 MS(Java/Others)    Memory Limit: 131072/65536 K (Java/Others)
Total Submission(s): 11840    Accepted Submission(s): 3617

Problem Description

Zero has an old printer thatdoesn't work well sometimes. As it is antique, he still like to use it to printarticles. But it is too old to work for a long time and it will certainly wearand 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 acost 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 articleperfectly.

 

 

Input

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

 

 

Output

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

 

 

Sample Input

5 5

5

9

5

7

5

 

 

Sample Output

230

 

题意:要输出n个数,输出的时候可以连续输出,每连续输出一串,它的花费为这串数字和的平方+M,求最小的费用

可以写出状态转移方程:dp[i]=dp[j]+M+(sum[i]-sum[j])^2;其中dp[i]表示输出i个时的最小花费,sum[i]表示从a[1]到a[i]的数字和

分析易知,时间复杂度为O(n^2)超时,故要通过斜率优化来降低时间复杂度。

实现思路如下:

令k<j<i,假设在j的时候决策比k好,即dp[j]+M+(sum[i]-sum[j])^2<dp[k]+M+(sum[i]-sum[k])^2,移项可得:(dp[j]+sum[j]^2-(dp[k]+sum[k]^2))/(2*(sum[j]-sum[k]))<sum[i]

令dp[j]+sum[j]^2=yj;令2*sum[j]=xj;则原式化为yj-yk/xj-xk<sum[i],容易发现方程左边正是我们熟悉的斜率表示

令yj-yk/xj-xk=f(j,k),那么如果f(j,k)<sum[i]成立就表明j的决策比k的决策要更优(即花费更少)

继续分析,还是令k<j<i;如果f(i,j)<f(j,k),那么j点便永远不可能成为最优解,可以直接排除在考虑范围之外,为什么呢?

(1). 当f(i,j)<sum[i]时,由上面的分析可得i比j更优;

(2). 当f(i,j)>=sum[i]时,得f(j,k)>sum[i] ,那么可以知道k点会比j更优

由这种方法可以排除不可能的点,即做到了优化、

下面开始寻找最优解

还是设k<j<i,由于前面我们排除了f(i,j)<f(j,k)的情况,所以整个有效点集呈现一种下凸性质,即k j的斜率要小于或等于j i的斜率(如下图)。


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

总结解法如下:

1.用单调队列维护最优解

2.假设队列中从头到尾已有k,j,i三个点,那么我们要使下一个p点入队列时,为了保证队列的下凸性质,如果f(p,i)<f(i,j),那么应该让i点出队,以此类推直到找到f(p,x)>=f(x,y),然后把p点放在该位置

3.求解时,假设队列中从头到尾已有k,j,i三个点,当求解p点时,如果f(j,k)<sum[p],那么说明j点比k点更优,k点出队,最后dp[p]=getdp(q[head])


#include <bits/stdc++.h>using namespace std;#define mst(a,b) memset((a),(b),sizeof(a))#define f(i,a,b) for(int i=(a);i<=(b);++i)const int maxn = 500005;const int mod = 9973;const int INF = 0x3f3f3f3f;const double eps = 1e-6;#define ll long long#define rush() int T;scanf("%d",&T);while(T--)int dp[maxn],q[maxn],sum[maxn];int n,m,head,tail;int getdp(int i,int j){    return dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]);  }int getup(int j,int k)   //yj-yk的部分{    return dp[j]+sum[j]*sum[j]-(dp[k]+sum[k]*sum[k]);}int getdown(int j,int k)  //xj-xk的部分{    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]=dp[0]=0;        for(int i=1;i<=n;i++)            sum[i]+=sum[i-1];        head=tail=0;        q[tail++]=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]))   //对应解法中的3                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]))  //对应解法中的2                tail--;            q[tail++]=i;        }        printf("%d\n",dp[n]);    }    return 0;}


0 0
原创粉丝点击