hdu3507 print article

hdu3507 print ariticle 斜率优化入门题

一个人要用n((0le nle 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
", dp[n]);
    }
    return 0;
}

  看完记得点个赞!

原文地址:https://www.cnblogs.com/MyNameIsPc/p/7611156.html