hdu3507 print article

hdu3507 print ariticle 斜率优化入门题

一个人要用n\((0\le n\le 500005)\)个单词打一篇文章。一个单词有一个价值\(C_i\),一个文章可以分成若干段,每一段消耗价值\((\sum_{i=1}^kC_i)^2+M\),其中M是常数。问如何安排单词打法,使得消耗的价值最小?

  首先,dp方程是\(dp[i]=dp[j]+M+(sum[i]-sum[j])^2\)

  但是,暴力转移是\(O(n^2)\)的。单调队列优化也用不了,因为dp方程内还有与i*j相关的项。咋办呢?当然是用斜率优化了!(什么鬼)

  来转换一下dp方程。假设目前dp的是i,j,k是能转移到i的阶段。如果从j转移比从k更优,说明:\(dp[j]+M+(sum[i]-sum[j])^2<dp[k]+M+(sum[i]-sum[k])^2\)

  推一下式子,可以推成\(dp[i]-dp[k]+2sum[i]sum[k]-2sum[i]sum[j]+sum[j]^2-sum[k]^2<0\)。为了不改变不等式的符号,我们让\(sum[j]>sum[k]\)。那么把式子除上\(2(sum[j]-sum[k])\),得到:\(\frac{dp[j]+sum[j]^2-(dp[k]+sum[k]^2)}{2(sum[j]-sum[k])}<sum[i]\),拿x和y数组代一下,\(\frac{y[j]-y[k]}{x[j]-x[k]}<sum[i]\ \ \ (x[j]-x[k]>0)\)

  现在我们来边缘op。首先,x[j]和y[j]都只和j本身有关,与转移无关。\(\frac{y[j]-y[k]}{x[j]-x[k]}<sum[i]\)中,我们发现只有sum[i]和i有关。我们成功分离出了与i有关的项!同时\(\frac{y[j]-y[k]}{x[j]-x[k]}\)很像斜率的表示,所以试着把所有\((x[j], y[j])\)画到坐标系上:

  图片

  对于一个点,它左上角的所有点,无论如何都会比它劣(边的斜率为负),同理,在它右下角的所有点,无论如何都会比它优。其它位置的点则不一定。因此,只有半下凸包上的点有可能是最优点。

  所以在转移过程中,我们要动态维护一个下凸包。每次决策的时候,在凸包上面二分就行了。

  但是!这道题很神奇。它满足决策单调性,并且新加入的点一定在所有点的右边。所以你既不用动态维护下凸包,也不用在凸包上面二分。用单调队列就行啦!

  看似这道题做完了,然而还有个坑点:\(c_i\)可能是0…这说明可能有多个点的x下标相同,甚至有可能多个点重合!当有多个点的x下标相同时,\(x[j]-x[k]=0\),也就是说如果你算斜率会被零除,然后光荣RE。所以计算斜率应该乘出来,避免这种情况。而当有多个点重合时,\(y[j]-y[k]=0<sum[i]\),也就是队列不会往前推。然而重合点不一定是最好的。所以在写程序的时候应该是小于等于。记住开始的式子\(dp[i]=dp[j]+M+(sum[i]-sum[j])^2\),斜率式是我们推出来的,只不过方便计算,还有一些情况被漏掉了!

#include <cstdio>
using namespace std;

const int maxn=5e5+5;
int n, m, h, t, sum[maxn];
int dp[maxn], x[maxn], y[maxn], q[maxn];
inline int sqr(int x){ return x*x; }

int main(){
    while (~scanf("%d%d", &n, &m)){
        for (int i=1; i<=n; ++i) scanf("%d", &sum[i]);
        for (int i=2; i<=n; ++i) sum[i]+=sum[i-1];
        sum[0]=h=t=dp[0]=x[0]=y[0]=0;
        for (int i=1; i<=n; ++i){
            while (h<t&&y[q[h+1]]-y[q[h]]
                   <=sum[i]*(x[q[h+1]]-x[q[h]])) ++h;
            dp[i]=dp[q[h]]+m+sqr(sum[i]-sum[q[h]]);
            x[i]=2*sum[i], y[i]=dp[i]+sqr(sum[i]);
            while (h<t&&(y[i]-y[q[t]])*(x[q[t]]-x[q[t-1]])
                   <=(y[q[t]]-y[q[t-1]])*(x[i]-x[q[t]])) --t;
            q[++t]=i;
        }
        printf("%d\n", dp[n]);
    }
    return 0;
}

  看完记得点个赞!

转载于:https://www.cnblogs.com/MyNameIsPc/p/7611156.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值