pengzhou讲完之后留了几道题......
[HNOI2008]玩具装箱toy
设c[i]是题目中c[i]的前缀和。
手推一下状态转移方程,f[i]=f[k]+(i-k-1+c[i]-c[k]-L)^2
搞一搞,x[k]=k+c[k],y[k]=f[k]+(k+c[k]+l+1)^2
这道题斜率单调递增,用队列维护下凸包即可。
1 #include<cstdio> 2 #define ll long long 3 4 int n,tk=1,pt=1; 5 ll l; 6 ll c[50005]; 7 ll f[50005]; 8 ll q[50005]; 9 10 ll y(int p) 11 { 12 return (ll)(f[p]+(ll)(p+c[p]+l+1)*(ll)(p+c[p]+l+1)); 13 } 14 15 ll x(int p) 16 { 17 return (ll)(p+c[p]); 18 } 19 20 int main() 21 { 22 scanf("%d%lld",&n,&l); 23 for(int i=1;i<=n;i++)scanf("%lld",&c[i]),c[i]+=c[i-1]; 24 for(int i=1;i<=n;i++) 25 { 26 while(tk<pt&&y(q[tk+1])-y(q[tk])<=2ll*(x(i))*(x(q[tk+1])-x(q[tk])))q[tk++]=0; 27 f[i]=f[q[tk]]+((ll)(i-q[tk]-1)+c[i]-c[q[tk]]-l)*((ll)(i-q[tk]-1)+c[i]-c[q[tk]]-l); 28 while(tk<pt&&(y(q[pt])-y(i))*(x(q[pt-1])-x(q[pt]))<=(y(q[pt-1])-y(q[pt]))*(x(q[pt])-x(i)))q[pt--]=0; 29 q[++pt]=i; 30 } 31 printf("%lld",f[n]); 32 }
[APIO2010]特别行动队
和上一道差不多。
设s[i]为士兵战斗力的前缀和。
(f[i]-a*si^2-b*si-c)=(f[j]+a*sj^2-b*sj)-(si)*(2*a*sj)
b=y-kx
b=f[i]-a*si^2-b*si-c,y=f[j]+a*sj^2-b*sj,k=si,x=2*a*sj
发现斜率k递增,x递减。
用队列维护上凸包即可。
1 #include<cstdio> 2 #define ll long long 3 4 int n; 5 ll a,b,c; 6 ll s[1000005]; 7 ll f[1000005]; 8 int q[1000005],tl=1,hd=1; 9 10 ll y(int p) 11 { 12 return f[p]+a*s[p]*s[p]-b*s[p]; 13 } 14 15 ll x(int p) 16 { 17 return 2ll*a*s[p]; 18 } 19 20 int main() 21 { 22 scanf("%d%lld%lld%lld",&n,&a,&b,&c); 23 for(int i=1;i<=n;i++)scanf("%lld",&s[i]),s[i]+=s[i-1]; 24 for(int i=1;i<=n;i++) 25 { 26 while(hd<tl&&y(q[hd])-y(q[hd+1])<=s[i]*(x(q[hd])-x(q[hd+1])))hd++; 27 f[i]=f[q[hd]]+a*(s[i]-s[q[hd]])*(s[i]-s[q[hd]])+b*(s[i]-s[q[hd]])+c; 28 while(hd<tl&&(y(q[tl-1])-y(q[tl]))*(x(q[tl])-x(i))>=(y(q[tl])-y(i))*(x(q[tl-1])-x(q[tl])))tl--; 29 q[++tl]=i; 30 } 31 printf("%lld",f[n]); 32 return 0; 33 }
[SDOI2016]征途
套路都是相似的呀~
这个多了一个维度,不慌。
显然先搞出来一个前缀和。
拿方差公式搞一搞,发现如果有某一天走了xi这么远,对方差的贡献为m*xi^2-2*xi*s[n]
而且无论怎么走,对方差的贡献都是在s[n]^2的基础上往上加的。
设f[i][j]为第j天走到第i个时候的贡献。
f[i][j]=f[k][j-1]+m(s[i]-s[k])^2-2*(s[i]-s[k])*s[n]
发现更新第j天的时候需要第j-1天的答案。
所以第一个循环枚举第几天,里面的循环枚举到了第几个。
斜率优化一下:
(f[i][j]+2*s[n]*s[i]-m*s[i]^2) = (f[k][j-1]+2*s[n]*s[k]+m*s[k]^2) - (s[i]) * (2*m*s[k])
b=y-k*x
用队列维护下凸包。
计算第j天的时候,把第j-1天的答案推到队列里。
注意计算第一天答案的时候不能更新队列。
第一天无论走到哪,都必须是从0走过来的。
所以必须保证计算第一天答案的时候队列里只有0。
1 #include<cstdio> 2 #define ll long long 3 4 int n,t; 5 ll m; 6 ll s[3005]; 7 ll f[3005][3005]; 8 int q[3005],hd,tl; 9 10 ll x(int p) 11 { 12 return 2ll*m*s[p]; 13 } 14 15 ll y(ll p) 16 { 17 return f[p][t-1]+2ll*s[n]*s[p]+m*s[p]*s[p]; 18 } 19 20 int main() 21 { 22 scanf("%d%lld",&n,&m); 23 for(int i=1;i<=n;i++)scanf("%lld",&s[i]),s[i]+=s[i-1]; 24 for(t=1;t<=m;t++) 25 { 26 hd=tl=1; 27 q[1]=0; 28 for(int i=1;i<=n;i++) 29 { 30 while(hd<tl&&y(q[hd+1])-y(q[hd])<=s[i]*(x(q[hd+1])-x(q[hd])))hd++; 31 f[i][t]=y(q[hd])-s[i]*x(q[hd])+m*s[i]*s[i]-2ll*s[i]*s[n]; 32 while(hd<tl&&(y(i)-y(q[tl]))*(x(q[tl])-x(q[tl-1]))<=(y(q[tl])-y(q[tl-1]))*(x(i)-x(q[tl])))tl--; 33 if(t>1)q[++tl]=i; 34 } 35 } 36 printf("%lld",f[n][m]+s[n]*s[n]); 37 return 0; 38 }