题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1010
题意:
有n条线段,长度分别为C[i]。
你需要将所有的线段分成若干组,每组中线段的编号必须连续。
然后每组中的线段接成一排,若线段的编号为i to j,则总长度X = j - i + ∑ C[i to j]。
对于每一个组,花费为(X - L)^2,其中L为给定常量。
问你最小总花费。
题解:
表示状态:
dp[i]表示已经将1 to i的线段分好组了,此时的最小总花费。
找出答案:
ans = dp[n]
如何转移:
设s[i] = ∑ C[1 to i], L = L + 1.
dp[i] = min dp[j] + (s[i]-s[j]- L)^2 (0 <= j < i)
边界条件:
dp[0] = 0
斜率优化:
设j < k,且k的决策更优。
则:dp[j] + (s[i]-s[j]- L)^2 > dp[k] + (s[i]-s[k]- L)^2
整理得:(dp[k]+(s[k]+L)^2-dp[j]+(s[j]+L)^2) / (2*(s[k]-s[j])) < s[i]
所以slope(i,j) = (dp[i]+(s[i]+L)^2-dp[j]+(s[j]+L)^2) / (2*(s[j]-s[k]))
由于s[i]递增,所以单调队列维护下凸壳即可。
AC Code:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #define MAX_N 50005 5 6 using namespace std; 7 8 int n,L; 9 int q[MAX_N]; 10 long long s[MAX_N]; 11 long long dp[MAX_N]; 12 13 inline double slope(int i,int j) 14 { 15 return (dp[i]+(s[i]+L)*(s[i]+L)-dp[j]-(s[j]+L)*(s[j]+L))/(2.0*(s[i]-s[j])); 16 } 17 18 int main() 19 { 20 cin>>n>>L; L++; 21 for(int i=1;i<=n;i++) cin>>s[i]; 22 for(int i=2;i<=n;i++) s[i]+=s[i-1]; 23 for(int i=1;i<=n;i++) s[i]+=i; 24 int l=1,r=1; 25 for(int i=1;i<=n;i++) 26 { 27 while(l<r && slope(q[l],q[l+1])<=s[i]) l++; 28 dp[i]=dp[q[l]]+(s[i]-s[q[l]]-L)*(s[i]-s[q[l]]-L); 29 while(l<r && slope(q[r],i)<slope(q[r-1],q[r])) r--; 30 q[++r]=i; 31 } 32 cout<<dp[n]<<endl; 33 }