斜率优化DP

pengzhou讲完之后留了几道题......

[HNOI2008]玩具装箱toy

bzoj 1010 传送门

洛谷P3195 传送门

设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 }
玩具装箱toy

 

[APIO2010]特别行动队

bzoj 1911 传送门

洛谷P3628 传送门

和上一道差不多。

设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]征途

bzoj 4518 传送门

洛谷P4072 传送门

套路都是相似的呀~

这个多了一个维度,不慌。

显然先搞出来一个前缀和。

拿方差公式搞一搞,发现如果有某一天走了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 }
征途

 

posted @ 2018-09-11 21:06  cervusky  阅读(157)  评论(0编辑  收藏  举报

Contact with me