dp斜率优化 Hdu 3507(Print Article)详细题解

来源:互联网 发布:鹿晗最新知乎 编辑:程序博客网 时间:2024/06/05 06:08

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=3507

累加器传送门:

http://blog.csdn.net/noiau/article/details/71775000

题目:

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.

输入:

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.

输出:

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

样例输入:

5 5
5
9
5
7
5

样例输出:

230


这篇博客本来写好了的…然后没有保存一下子不小心把浏览器关了…所以重新写一遍吧


分析题目

分割出最小值

所以可以想到一个很简单的dp转移方程

dp[i]=min(dp[i],dp[j]+(cnt[i]-cnt[j])^2+M);

(伪代码)


其中dp[i]表示把前i个数分解成若干段所能获得的最小值,可以枚举j 然后把i到j切成一段需要花费(cnt[i]-cnt[j])^2和一个M

cnt数组维护的是前缀和

把括号打开,M可以直接拿出来

dp[i]=min(dp[i],dp[j]+cnt[i]^2-2*cnt[i]*cnt[j]+cnt[j]^2)+M

M就扔了,最后再加上去,因为M是个常量,无论怎么决策都不影响M的值


而对于一个i来说,cnt[i]^2也一定是确定的

所以再把cnt[i]^2拿出来

dp[i]=min(dp[i],dp[j]-2*cnt[i]*cnt[j]+cnt[j]^2)+M+cnt[i]^2;

cnt[i]^2也扔了,最后再加上去,也不影响


所以

dp[i]=dp[j]-2*cnt[i]*cnt[j]+cnt[j]^2;

如果我们令b=dp[i],y=dp[j]+cnt[j]^2,x=cnt[j],k=2*cnt[i];

原来的式子转化为

b=y-kx;

移一下项可以得到

y=kx+b;

我们会发现一个很神奇的事情,那就是,y只和j有关,x也只和j有关,而k是一个可以在O(1)的时间内计算出来的和i有关的值,所以我们只需要求在一个固定斜率k的条件下,找有序数对(x,y)使得b最小,因为b就是我们要求的dp[i],所以在坐标系中有一系列的点(x,y),其中x,y是之前的j所留下来的点(计算i的时候,j的范围是第1个点到底i-1个点),因为我们每次计算了一次i之后,就把i当做i+1的j来进入到单调队列中
此时我们需要想象一条直线,它的斜率固定,从很下方,往y轴正方向移动,与第一个点相交时与y轴的结局就是我们要求的b,也就是dp[i]的最优值,用单调队列来存储点就行了


那么对于这样的一个单调队列,我们是怎么进行head++和tail - - 的操作的呢?

这里写图片描述

现在我添加了一个P点进来


因为直线y=kx+b在往上平移,这个时候遇到的第一个点是B,所以以后再也不可能遇见A,原因很简单,因为我们的k是有单调性的,也就是说,k是单调递增的,既然斜率都在变大,直线都变陡了,在此时的k都与A不相交,以后怎么会相交呢?所以A出队
用while来判断即可


Tail - -;

tail - - 可能要相对复杂那么一点点,要维护一个凸包,也就是说,添加P点的时候发现,如果BCDP都在队列里的话,很明显这个是不单调的,所以只需要删除B点,即可保持队列的单调性,这里需要用到向量的叉积,我在另一篇博客里写过,如果不会用叉积判断两个向量的位置关系的话,可以看我的这篇博客:
http://blog.csdn.net/noiau/article/details/71785500


进行了head和tail的操作了之后的单调队列应该是这样的
这里写图片描述
此时队列里就只剩BCP了
取B点,也就是队列的head,就可以获得最小值啦

所以如果一旦向量CP在向量CD的顺时针方向,就tail - - 了;


下面贴上代码

#include<cstdio>#include<iostream>#include<cstring>#define MAXN 500000+10#define LL long long#define LARGE 23333333using namespace std;int N,M;LL dp[MAXN];struct Point{    LL x,y;}A,B,C;typedef Point P;P q[MAXN];P operator - (P a,P b){    P pp;    pp.x=a.x-b.x;    pp.y=a.y-b.y;    return pp;}LL operator * (P a,P b){    return a.x*b.y-a.y*b.x;}double calck(int head){    return (double)(((double)(q[head+1].y-q[head].y))/((double(q[head+1].x-q[head].x))));}int head,tail;LL cnt[MAXN];void insert(LL x,LL y){    P p1;p1.x=x,p1.y=y;    while(head<tail&&(p1-q[tail-1])*(q[tail]-q[tail-1])>=0){        tail--;    }    q[++tail]=p1;}bool init(){    if(scanf("%d%d",&N,&M)==EOF) return false;    memset(q,0,sizeof(q));    head=1;tail=2;    int temp;    cnt[0]=0;    for(int i=1;i<=N;i++){        scanf("%d",&temp);        cnt[i]=cnt[i-1]+temp;    }    //令b=dp[i],y=dp[j]+cnt[j]^2,x=cnt[j],k=2*cnt[i]    dp[0]=0;    dp[1]=M+cnt[1]*cnt[1];    q[1].x=0;q[1].y=0;    q[1].x=cnt[1];    q[1].y=dp[1]+cnt[1]*cnt[1];    return true;}void dpp(){    for(int i=2;i<=N;i++){        LL k=2*cnt[i];        //double k2=calck(head);        while(head<tail&&q[head].y-k*q[head].x > q[head+1].y-k*q[head+1].x){            head++;            //k2=calck(head);        }        dp[i]=q[head].y-k*q[head].x+cnt[i]*cnt[i]+M;//y=kx+b,b=y-kx;        insert(cnt[i],dp[i]+cnt[i]*cnt[i]);    }}void print(){    cout<<dp[N]<<endl;}int main(){    while(init()){        dpp();        print();    }    return 0;}

这里写图片描述

读者可以注意到,原来这里是有一个calck()函数的,用来计算斜率,然而为什么最后又没有用calck()函数呢?是因为在计算的时候产生了偏差,所以导致判断的时候出现错误,所以我们在做判断的时候,尽量不要用除法,如果实在要用除法的,要注意eps的使用,以消除一定的误差

Tail的另一种判断

不用判断向量的叉积,而是判断斜率,也就是加入P点,AP直线和AB直线的斜率来维护凸包,然而向量显然满足只用乘法而且较为简单
这里写图片描述

斜率优化练习题:

No.1 Hdu 1300
http://blog.csdn.net/noiau/article/details/71817596
No.2 Hdu 3480
http://blog.csdn.net/NOIAu/article/details/72428364
No.3 Hdu 2829
http://blog.csdn.net/NOIAu/article/details/72369084
No.4 CEOI 2003
http://blog.csdn.net/NOIAu/article/details/72323321
No.5 bzoj 1010
http://blog.csdn.net/NOIAu/article/details/72028519
No.6 Hdu 3045
http://blog.csdn.net/NOIAu/article/details/71862162

1 0
原创粉丝点击