BZOJ 1010 dp 斜率优化

题意: 给一段数列,要求分成若干段,有一个公式可算每段的花费,求整段数列最小花费。

题意很裸,但是转移方程容易写错,题中给出一个计算公式,表示从i到j打一个包所消耗费用,我刚开始时的想法如下:

dp[i]=min{dp[j]+(sum[i]-sum[j-1]+i-j-l)^2}(j<i); 看起来好像很有道理,但是是错的,因为此时dp[i]表示从i到i-1打过包了,

然而到了dp[n],问题来了,第n个点并没有更新,要想是上述思路正确,不能直接套公式,我下面写出更简单的思路:

dp[i]表示当前点以前有若干个包,就可以方便的写出方程:dp[i]=min{dp[j]+(sum[i]-sum[j]+i-j-1-l)^2};

这就对啦!

但是数据范围很大,o(n^2)过不了,于是就用斜率优化变成n的,就过了。

下面详细讲一下斜率优化:

  斜率优化就是维护一个凸壳,使其严格上凸或下凸,具体情况具体分析;

  设k,j,且j<k<i,i由k更新而来,则化简后的式子:

  dp[k]+(sum[k]+k+1+l)^2-dp[j]-(sum[j]+j+1+l)^2/(2*(sum[k]+k-sum[j]-j))<=sum[i]+i;

  观察,这个式子左边是一个与k,j有关的斜率表达式,而右边只含有i,显然右边单增,而题中要找min,要想保持现在的k更优,

  则斜率k<斜率原,所以要维护下凸性,就可用一个单调队列来维护,降到n的复杂度了。

下面是代码:

 1 /**************************************************************
 2     Problem: 1010
 3     User: 201484348
 4     Language: C++
 5     Result: Accepted
 6     Time:120 ms
 7     Memory:5492 kb
 8 ****************************************************************/
 9  
10 #include<cstdio>
11 #include<iostream>
12 using namespace std;
13 typedef long long ll;
14 struct data{
15     ll x,y;
16 }cmp,D[50010];
17 int L,R,N,W;
18 ll C[500010];
19 inline ll jud(data a,data b,data c)
20 {
21     return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
22 }  
23 int main()
24 {
25     scanf("%d%d",&N,&W);
26     for (int i=1;i<=N;i++)
27     {
28         scanf("%lld",&C[i]);
29         C[i]+=C[i-1];
30     }
31     for (int i=1;i<=N;i++)
32     {
33         while (L<R&&D[L].y-2*(i+C[i]-W-1)*D[L].x>=D[L+1].y-2*(i+C[i]-W-1)*D[L+1].x) L++;
34         cmp.x=i+C[i];cmp.y=D[L].y-2*(i+C[i]-W-1)*D[L].x+(i+C[i]-W-1)*(i+C[i]-W-1)+(i+C[i])*(i+C[i]);
35         while (L<R&&jud(D[R-1],D[R],cmp)<=0) R--;
36         D[++R]=cmp;
37     }
38     printf("%lld\n",D[R].y-(N+C[N])*(N+C[N]));
39     return 0;
40 }
View Code

 

posted @ 2015-12-20 16:15  聂渣渣  阅读(147)  评论(0编辑  收藏  举报