斜率优化DP(附hdu3507题解)

Print Article

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


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 559575
 
Sample Output
 
 
230
 

Author
Xnozero
 

Source



题意:
给你n个数,问你怎样顺序输出,使得总价值最小。
每一行价值计算公式是上图所示,这里每一行可以输任意个字符,你可将n个字符任意(但按顺序)输出到不同行,
其实类似于切蛋糕,给你n个字符串,问你在中间怎么切,可以切任意刀,使得总价值最小

解析:
这里就是用了斜率优化DP
这个东西理解起来可能有点难,并且网上很多技术博客书写的时候都有点小错误。
本人比较渣,我这里就推荐一个博客,然后说一些它上面的 书写的错误,方便理解。
这上面还有另外两篇大佬的博客也值得去看一下。
这里应该把MAX改成MIN
 我觉得首先应该理解的是找答案的这部分,因为这部分在代码中是在插入I前面的,不过这只是个人看法。
并且这里有一个致命的错误,误导了我很久就是 这里应该是
kj的斜率<2S[t+1],这里就是当前kj的斜率是小于2S[t+1]的最大斜率

找答案的过程,就是向上述大佬所说的,从头开始,找当前存在数组中不大于2S[t+1]的最大的斜率。因为这道题2S[t+1]是有单调性的,随着t增加而增加,所以说当你前面找过的S[t0+1]的最优斜率,很大机率不是(除了S[t0+1]=S[t+1])S[t+1]的最优解。所有用队列去存,找答案的时候,就在开头找最优解,不满足当前S[t0+1]的解就删掉(因为当前都不是最优解,后面也肯定不是最优解),这样用队列就很方便O(n)的复杂度,而如果不单调,就要用到二分找答案这样总的复杂度就要达到O(nlogn)。

最后这一块内容是当对于i的最优解找到是,将i插入到队列时为在i后面的点服务的情况(k<j<i)。这里就是在i插入后,对于后面的点来说,明显不可能是最优解的点j删掉(最优解只可能是k或i),这一项工作是从队列的结尾开始的。这里的一个小错误就是第一句话
应该是f[x]=dp[x]-S[x]^2


这里还有一点我没搞懂的是为什么在第二个插入操作时,一定要<=而不用<,即一定要把斜率相同的kj=ji,删掉j点。我感觉不删掉也只是在后面 操作查询(因为在查询中也有加=)和插入中多耗点时间,但交上去就WA了,不知道为什么?

总的来说,感谢上面的博客大佬的教导,斜率优化DP其实将对于dp[i]的找答案的过程进行了优化,使得不需要将比i小的点全部遍历一遍。

复杂度: O(n),如果S[i]不单调,那么就只能用CDQ分治来做,复杂度O(nlogn)



#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 5e5 +100;

int sum[MAXN];

int dp[MAXN];
int q[MAXN],head,tail;
int n,m;

int DP(int i,int j)
{
    return dp[j]+(sum[i]-sum[j])*(sum[i]-sum[j])+m;
}

int UP(int i,int j)
{
    return dp[i]+sum[i]*sum[i]-dp[j]-sum[j]*sum[j];
}


int DOWN(int i,int j)
{
    return sum[i]-sum[j];
}

int main()
{

    while(scanf("%d%d",&n,&m)!=EOF)
    {
        sum[0]=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&sum[i]);
        }

        for(int i=1;i<=n;i++)
        {
            sum[i]+=sum[i-1];
        }
        dp[0]=0;
        tail=head=0;
        q[tail++]=0;

        for(int i=1;i<=n;i++)   //head+1<tail保证队列里面至少有一个点
        {
            while(head+1<tail&&UP(q[head+1],q[head])<=2*sum[i]*DOWN(q[head+1],q[head]))
                head++;
            dp[i]=DP(i,q[head]);

            while(head+1<tail&&UP(i,q[tail-1])*DOWN(q[tail-1],q[tail-2])<=UP(q[tail-1],q[tail-2])*DOWN(i,q[tail-1]))
                tail--;

            q[tail++]=i;
        }       //等号是为了将比较传递下去,第一个等号可加可不加,第二个必须加!

        printf("%d\n",dp[n]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值