●BZOJ 4518 [Sdoi2016]征途
题链:
http://www.lydsy.com/JudgeOnline/problem.php?id=4518
题解:
斜率优化DP
首先看看最后答案的形式:
设a[i]为第i天走的距离,那么
$ANS=\frac{\sum_{i=1}^{M}(a[i]-\overline{x})^2}{M}\times{M^2}$
$\;\qquad=\frac{(\sum_{i=1}^{M}a[i]^2)-2\overline{x}SUM+M\overline{x}^2}{M}\times{M^2}$
$\;\qquad=M(\sum_{i=1}^{M}a[i]^2)-SUM^2$
由于M和SUM是固定的,所以问题转化为求$\sum_{i=1}^{M}a[i]^2$的最小值,
即把区间分为M段,使得每一段的和的平方加起来最小。
定义 DP[i][j] 为前i个位置,分为了j段,且i位置为最后一段的结尾的最小值。
转移:
$DP[i][j]\,=\,min(DP[k][j-1]+(SUM[i]-SUM[k])^2)$
然后把式子展开,得到:
$DP[i][j]\,=\,min(DP[k][j-1]+SUM[k]^2-2SUM[i]SUM[k]+SUM[i]^2)$
是一个典型的可以用斜率优化的式子。
(由于DP时是先枚举第二维,一层一层地计算,所以以下的内容中省略掉dp的第二维,同时用g[i]表示上一层的dp[i][~])
令$Y[j]=g[j]+SUM[j]^2$,
若对于当前计算的dp[i],存在两个转移来源点 k,j,k < j,且j优于k
则得到
$Y[j]-2SUM[i]SUM[j]-Y[k]-2SUM[i]SUM[k]<0$
化简:$\frac{Y[j]-Y[k]}{2SUM[j]-2SUM[k]}<SUM[i]$
令Slope(j,k)=$\frac{Y[j]-Y[k]}{2SUM[j]-2SUM[k]}$,
则得到结论:若k < j,且Slope(j,k)<SUM[i],则j优于k。
那么如果存在 k<j<i,且Slope(i,j)<Slope(j,k),则j是无效点,舍去。
同时注意到SUM[i]单增,所以可以用单调队列维护。
最终的复杂度 O(N*M)
代码:
#include<cstdio> #include<cstring> #include<iostream> #define MAXN 3050 using namespace std; int DP[2][MAXN],SUM[MAXN]; int N,M,*t1=DP[0],*t2=DP[1]; struct Moque{ int q[MAXN],l,r; void Reset(){l=r=1; q[1]=0; t2[0]=0;} double Y(int j){ return t2[j]+1.0*SUM[j]*SUM[j]; } double X(int j){ return 2.0*SUM[j]; } double Slope(int j,int k){ return (Y(j)-Y(k))/(X(j)-X(k)); } void Push(int i){ if(l<=r&&SUM[i]==SUM[q[r]]) {if(t2[i]<t2[q[r]]) r--; else return;} while(l+1<=r&&Slope(i,q[r])<Slope(q[r],q[r-1])) r--; q[++r]=i; } int Query(int i){ while(l+1<=r&&Slope(q[l],q[l+1])<SUM[i]) l++; return q[l]; } }Q; int main(){ scanf("%d%d",&N,&M); for(int i=1;i<=N;i++) scanf("%d",&SUM[i]),SUM[i]+=SUM[i-1]; memset(DP,0x3f,sizeof(DP)); t1[0]=0; for(int j=1;j<=M;j++){ Q.Reset(); swap(t1,t2); for(int i=1,k;i<=N;i++){ Q.Push(i); k=Q.Query(i); t1[i]=t2[k]+(SUM[i]-SUM[k])*(SUM[i]-SUM[k]); } } printf("%d",M*t1[N]-SUM[N]*SUM[N]); return 0; }
Do not go gentle into that good night.
Rage, rage against the dying of the light.
————Dylan Thomas