斜率优化dp练习
1.HDU3507
裸题,有助于理解斜率优化的精髓。
dp[i]=min(dp[j]+m+(sum[i]-sum[j])2)
很显然不是单调队列。
根据斜率优化的的定义,就是先设两个决策j,k
什么时候我们认为在 i 的环境下 j 比 k 好呢?根据上面的递推式,得到下面这么一个式子
dp[j]+m+(sum[i]-sum[j])2<dp[k]+m+(sum[i]-sum[k])2
打开括号:
dp[j]+m+sum[i]2+sum[j]2-2*sum[i]*sum[j]<dp[k]+m+sum[i]2+sum[k]2-2*sum[i]*sum[k]
移项,将有 i 的项移到右侧:
dp[j]+sum[j]2-dp[k]-sum[k]2<2*sum[i]*(sum[j]-sum[k])
除下来:
(dp[j]+sum[j]2-dp[k]-sum[k]2)/[2*(sum[j]-sum[k])]<sum[i]
好了这就是斜率了^_^
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 typedef long long lnt; 5 int Q[1000000]; 6 lnt sum[1000000]; 7 lnt dp[1000000]; 8 int h,t; 9 lnt n; 10 lnt m; 11 lnt X(int x) 12 { 13 return dp[x]+sum[x]*sum[x]; 14 } 15 lnt tp(int i,int j) 16 { 17 return X(i)-X(j); 18 } 19 lnt btm(int i,int j) 20 { 21 return 2*sum[i]-2*sum[j]; 22 } 23 int main() 24 { 25 while(scanf("%lld%lld",&n,&m)!=EOF) 26 { 27 sum[0]=0; 28 for(int i=1;i<=n;i++) 29 scanf("%lld",&sum[i]); 30 for(int i=1;i<=n;i++) 31 sum[i]+=sum[i-1]; 32 h=t=1; 33 Q[1]=0; 34 dp[0]=0; 35 for(int i=1;i<=n;i++) 36 { 37 while(h<t&&(tp(Q[h+1],Q[h])<=sum[i]*btm(Q[h+1],Q[h]))) 38 h++; 39 dp[i]=dp[Q[h]]+m+(sum[i]-sum[Q[h]])*(sum[i]-sum[Q[h]]); 40 while(h<t&&(tp(Q[t],Q[t-1])*btm(i,Q[t])>=tp(i,Q[t])*btm(Q[t],Q[t-1]))) 41 t--; 42 Q[++t]=i; 43 } 44 printf("%lld\n",dp[n]); 45 } 46 return 0; 47 }
这道题依然斜率单调
dp方程自己推:
dp[i]=min(dp[j]+(i-j-1+sum[i]-sum[j]-L)2)
依然假设在 i 的环境下决策 j 优于 k
那么:
dp[j]+(i-j-1+sum[i]-sum[j]-L)2<dp[k]+(i-k-1+sum[i]-sum[k]-L)2
将常数项与与 i 有关的项放到一起,展开:
(sum[j]+j)2-2*(i+sum[i]-L-1)*(sum[j]+j)+dp[j]<(sum[k]+k)2-2*(i+sum[i]-L-1)*(sum[k]+k)+dp[k]
设函数 f(x)=sum[x]+x , h(x)=x+sum[x]-L-1 , g(x)=dp[x]+f(x)2
得到当:
g(j)-g(k)<2*h(i)*(f(j)-f(k)) 时,j 比 k 优秀。
f(x)单调递增,斜率单调。
时间复杂度O(n)
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 typedef long long lnt; 5 lnt sum[1000000]; 6 lnt dp[1000000]; 7 int Q[1000000]; 8 int h,t; 9 int n; 10 lnt L; 11 lnt f(int x) 12 { 13 return (sum[x]+(lnt)(x)); 14 } 15 lnt k(int x) 16 { 17 return ((lnt)(x)+sum[x]-L-1); 18 } 19 lnt g(int x) 20 { 21 return (dp[x]+f(x)*f(x)); 22 } 23 lnt squ(lnt x) 24 { 25 return x*x; 26 } 27 int main() 28 { 29 scanf("%d%lld",&n,&L); 30 for(int i=1;i<=n;i++) 31 { 32 scanf("%lld",&sum[i]); 33 sum[i]+=sum[i-1]; 34 } 35 Q[1]=0; 36 dp[0]=0; 37 h=t=1; 38 for(int i=1;i<=n;i++) 39 { 40 while(h<t&&g(Q[h+1])-g(Q[h])<2ll*k(i)*(f(Q[h+1])-f(Q[h]))) 41 h++; 42 dp[i]=dp[Q[h]]+squ((lnt)(i-Q[h]-1)+sum[i]-sum[Q[h]]-L); 43 while(h<t&&(((g(Q[t-1])-g(Q[t]))*(f(Q[t])-f(i)))>((g(Q[t])-g(i))*(f(Q[t-1])-f(Q[t]))))) 44 t--; 45 Q[++t]=i; 46 } 47 printf("%lld\n",dp[n]); 48 return 0; 49 }
斜率单调。
方程自己推:
设:g(x)=a*x2+b*x+c
dp[i]=max(dp[j]+g(sum[i]-sum[j]))
设在 i 环境下决策 j 优于 k
dp[j]+g(sum[i]-sum[j])>dp[k]+g(sum[i]-sum[k])
设f(x)=dp[x]+a*sum[x]2-b*sum[x]
则当:
f(j)-f(k)>2*a*sum[i]*(sum[j]-sum[k])
设 j > k 斜率单调
时间复杂度O(n)
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdio> 4 typedef long long lnt; 5 lnt sum[1000006]; 6 lnt dp[1000006]; 7 int Q[1000006]; 8 int h,t; 9 int n; 10 lnt a,b,c; 11 lnt f(int x) 12 { 13 return dp[x]+a*sum[x]*sum[x]-b*sum[x]; 14 } 15 lnt g(lnt x) 16 { 17 return a*x*x+b*x+c; 18 } 19 int main(void) 20 { 21 sum[0]=0; 22 h=t=1; 23 scanf("%d",&n); 24 scanf("%lld%lld%lld",&a,&b,&c); 25 for(int i=1;i<=n;i++) 26 { 27 scanf("%lld",&sum[i]); 28 sum[i]+=sum[i-1]; 29 } 30 Q[1]=0; 31 dp[0]=0; 32 for(int i=1;i<=n;i++) 33 { 34 while(h<t&&(f(Q[h+1])-f(Q[h]))>2*a*sum[i]*(sum[Q[h+1]]-sum[Q[h]])) 35 h++; 36 dp[i]=dp[Q[h]]+g(sum[i]-sum[Q[h]]); 37 while(h<t&&(f(Q[t])-f(Q[t-1]))*(sum[i]-sum[Q[t]])<=(f(i)-f(Q[t]))*(sum[Q[t]]-sum[Q[t-1]])) 38 t--; 39 Q[++t]=i; 40 } 41 printf("%lld\n",dp[n]); 42 return 0; 43 }
这次变成二维的了。
都一样,展开方程+斜率优化。
这次要对于每一天进行O(n)转移,共m天,时间复杂度O(n*m)
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 typedef long long lnt; 5 lnt dp[4000][4000]; 6 lnt sum[10000]; 7 int Q[1000000]; 8 lnt n,m; 9 lnt L; 10 int h,t; 11 lnt squ(lnt x) 12 { 13 return x*x; 14 } 15 lnt f(int x,int i) 16 { 17 return dp[x][i]+m*squ(sum[x])+2ll*L*sum[x]; 18 } 19 int main() 20 { 21 scanf("%lld%lld",&n,&m); 22 for(int i=1;i<=n;i++) 23 { 24 scanf("%lld",&sum[i]); 25 sum[i]+=sum[i-1]; 26 } 27 L=sum[n]; 28 for(int i=1;i<=n;i++) 29 { 30 dp[i][1]=m*squ(sum[i])-2ll*L*sum[i]; 31 } 32 for(int d=2;d<=m;d++) 33 { 34 t=h=1; 35 Q[1]=0; 36 for(int i=1;i<=n;i++) 37 { 38 while(h<t&&(f(Q[h+1],d-1)-f(Q[h],d-1))<2ll*m*sum[i]*(sum[Q[h+1]]-sum[Q[h]])) 39 h++; 40 dp[i][d]=dp[Q[h]][d-1]+m*squ(sum[i]-sum[Q[h]])-2ll*L*(sum[i]-sum[Q[h]]); 41 while(h<t&&(f(Q[t],d-1)-f(Q[t-1],d-1))*(sum[i]-sum[Q[t]])>(f(i,d-1)-f(Q[t],d-1))*(sum[Q[t]]-sum[Q[t-1]])) 42 t--; 43 Q[++t]=i; 44 } 45 } 46 printf("%lld\n",dp[n][m]+squ(L)); 47 return 0; 48 }