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 }