hdu3507Print Article(斜率)

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

分析:
第二道斜率优化,摸索出了一点套路

说实话题目就看蒙了。。。
题意:将序列a[n]分成若干段,每段的值为(∑a[i])^2 + M. 求序列的最小值

做优化dp首先需要的就是状态转移方程

f[i]表示到第i个数的最优值
f[i]=min{f[j]+(sum[i]-sum[j])^2+m} (前一段最后一个数是j)

假设 k < j < i
f[j]的状态比k更优
则一定有

f[j]+(sum[i]-sum[j])^2+m < f[k]+(sum[i]-sum[k])^2+m
f[j]+sum[j]^2-2*sum[i]*sum[j] < f[k]+sum[k]^2-2*sum[i]*sum[k]
(f[j]+sum[j]^2)-(f[k]+sum[k]^2) < 2*sum[i]*(sum[j]-sum[k])
移项,得

((f[j]+sum[j]^2)-(f[k]+sum[k]^2))/(2*(sum[j]-sum[k])) < sum[i]

设y=f[]+sum[]^2,x=sum[]
则原式=>
y[j]-y[k]/x[j]-x[k] < sum[i]
设g(j,k)=y[j]-y[k]/x[j]-x[k]
g(j,k) < sum[i]
说明f[j]的状态比f[k]更优(横坐标更大的更优)

那我们能不能利用这个性质去掉一些状态呢
假设 k < j < i
若g(j,k)>g(j,i),那么j这个状态可以去掉
证明:
1.g(i,j) < sum[i],i比j更优
2.g(i,j) >=sum[i],则g(j,k)>g(i,j)>=sum[i],j比i优而k比j更优

这样我们一旦遇到了g(j,k)>g(j,i)就可以去掉j这种状态了,

这样我们的状态转移可以变成:
1.维护一个双端优先队列
2.维护f,在队首取元素,若g(q[tou],q[tou+1])<=sum[i],tou–
因为tou+1的状态更优,直到g(q[tou],q[tou+1])>sum[i],
就用q[tou]更新f
3.在队尾加入f[i],若g(i,q[wei])<=g(q[wei],q[wei-1]),wei–,
(g(j,k)>g(j,i)就可以去掉j这种状态了),直到g(i,q[wei])>g(q[wei],q[wei-1]),加入i

tip:

注意初始状态:
f[0]=0;
f[1]=sum[1]*sum[1]+m; //f[1]=f[0]+(sum[1]-sum[0])^2+m
wei=1; tou=0; q[wei]=1; q[tou]=0;

这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N=500005;
int n,m;
int q[N],tou,wei,sum[N],f[N];

double xl(int x,int y)
{
    return (double)((f[x]+sum[x]*sum[x])-(f[y]+sum[y]*sum[y]))/(double)(2*f[x]-2*f[y]);
}

void doit()
{
    f[0]=0; f[1]=sum[1]*sum[1]+m;  //初始状态很重要 
    wei=1; tou=0;
    q[wei]=1; q[tou]=0;
    for (int i=2;i<=n;i++)
    {
        while (tou<wei&&xl(q[tou],q[tou+1])<=sum[i]) tou++;
        f[i]=f[q[tou]]+(sum[i]-sum[q[tou]])*(sum[i]-sum[q[tou]])+m;
        while (tou<wei&&xl(i,q[wei])<=xl(q[wei],q[wei-1])) wei--;
        q[++wei]=i;
    }
    printf("%d",f[n]);
}

int main()
{
    while (~(scanf("%d%d",&n,&m)))
    {
        int u;
        for (int i=1;i<=n;i++) scanf("%d",&u),sum[i]=sum[i-1]+u;
        doit();
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值