关于斜率优化,我就是一个傻子啊,真的一直没弄懂……php
状态和方程仍是很好出来的啊:数组
$f[i]=min(f[j]+(s[i]-s[j]+i-j-L-1)^2)$其中$s[i]$表示前缀和,$f[i]$表示前$i$个处理后的最小值。数据结构
可是咱们发现,这个东西要转移的话是个$O(n^2)$而$N<=50000$,显然转移不了。优化
接下来就是斜率优化的天下了!spa
咱们要找的是在$i$以前的一个$j$使得$f[i]$最小,考虑怎样能够快速的找到。3d
令$a[i]=s[i]+i,b[i]=s[i]+i-L-1$。code
咱们将原方程变形,就能够获得这样的形式:(先不要管为啥好吧)blog
$2*a[i]*b[j]+f[i]-a[i]^2=f[j]+b[j]^2$。先明确一点,当咱们在转移$i$时$a[i]$是肯定的。队列
对应一下$kx+b=y$,咱们令$2*a[i]$为$k$,$b[j]$为$x$,$f[j]+b[j]^2$为$y$,那么,咱们所求为直线在$y$上的最小截距。
当咱们在转移$i$时,前面的$1~i-1$可表示为一堆点($P_j(b[j],f[j]+b[j]^2)$)。
转移即为,找一条过$P_j$的斜率为$2*a[i]$的直线,使得其在$y$轴上的截距最小。
不妨拿出三个点,其构成一个向上的凸包(如右上的图)咱们发现,当咱们平移直线是,$B$必定不为最优决策。
因此咱们能够舍掉$B$点,即维护一个向下的凸包(以下面的图)这是咱们发现,此时的$A,B,C$三点都有可能成为最优决策。
此时考虑用一个数据结构维护全部能够构成一个向下的凸包的点。
//回来看下以前咱们说的斜率$2*a[i]$,显然它具备单调性(若是没有单调性就二分)
在前$1~i-1$中,不妨设有两个状态:$j,k$且$j$比$k$更优,则有:
$f[j]+(s[i]-s[j]+i-j-L-1)^2<f[k]+(s[i]-s[k]+i-k-L-1)^2$
通过一系列绝不人道的化简(风骨傲天很懒因此他没把将过程放上来)能够获得:
$2*a[i]>\frac{f[j]-f[k]+b[j]^2-b[k]^2}{b[j]-b[k]}$(好丑的式子……)
也就是说只要知足这个式子,就有$j$比$k$更优。
一样对应斜率,就有:$P_j$和$P_k$的斜率小于$2*a[i]$时,就有$j$比$k$更优。
又由于咱们维护的是一个向下的凸包,因此咱们就只用考虑相邻的两个点便可。
那么咱们就能够用一个单调队列来维护,队列中相邻点间连线的斜率递增。
这张图应该说很好的反应了转移和维护的过程,上面的两张就是转移,下面的两张是维护。
转移:由图中咱们能够发现,咱们要求的$j$为斜率第一个大于$2*a[i]$的点,所以舍掉$A,B$
维护:由于单桥队列中斜率的单调性,删掉$E$,由转移后的$i$得出的点$F(b[i],f[i]+b[i]^2)$加入队尾。
而实际上,咱们的斜率优化有必定的公式:
当方程形如:$f[i]=min(f[j]+S(i,j))+k$($k$为常数)时,可使用斜率优化。咱们的斜率为在当次转移中的一个不变量,维护的是一堆点。不过一般用上面讲到的“假设两个状态法”来求斜率。
因此斜率优化的思考过程应该是和上面讲到的相反,先考虑斜率再变形方程并得出$x,y$。
啊~终于打完了,累成狗
又是快乐的代码时间:
#include<bits/stdc++.h> using namespace std; inline int read() { int f=1,w=0;char x=0; while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();} while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();} return w*f; } const int N=50010; int n,q[N],l,r,L; double s[N],f[N]; inline double A(int i) {return s[i]+i;} inline double B(int i) {return s[i]+i+L+1;} inline double S(double x) {return x*x;} inline double K(int i,int j) {return (f[i]-f[j]+S(B(i))-S(B(j)))/(B(i)-B(j));} int main(){ #ifndef ONLINE_JUDGE freopen("A.in","r",stdin); #endif n=read();L=read();l=r=1; for(int i=1,c;i<=n;i++) c=read(),s[i]=c*1.0+s[i-1]; for(int i=1;i<=n;i++) { while(l<r&&2*(s[i]+i)>K(q[l],q[l+1])) l++; f[i]=f[q[l]]+S(A(i)-B(q[l])); while(l<r&&K(q[r-1],q[r])>K(i,q[r-1])) r--; q[++r]=i; } printf("%lld",(long long)f[n]); }
这一题也是一个经典的斜率优化,稍微有点不一样。
状态:$f[i]=min(f[j]+a*(s[i]-s[j])^2+b*(s[i]-s[j])+c)$
化简:$2as[i]s[j]+f[i]-as[i]^2-bs[i]-c=f[j]+as[j]^2-b*s[j]$
由于这一题的$a<0$,因此咱们维护一个向上的凸包便可。(可是$a<0$时仍有单调性)
直接上代码:
#include <cstdio> using namespace std; #define F(x) ((x)*(x)) inline int read() { int f=1,w=0;char x=0; while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();} while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();} return w*f; } const int N=1000010; #define int long long int n,l,r,f[N],Q[N],a,b,c,s[N]; inline double Work(int x,int y) { return 1.*(f[x]-f[y]+(F(s[x])-F(s[y]))*a)/(s[x]-s[y])-b; } main(){ n=read(),a=read(),b=read(),c=read(); for(int i=1;i<=n;i++) s[i]=read(),s[i]+=s[i-1]; for(int i=1;i<=n;i++) { while(l<r&&Work(Q[l+1],Q[l])>s[i]*2*a) l++; //用于从队列中选出最值 f[i]=f[Q[l]]+a*F(s[i]-s[Q[l]])+b*(s[i]-s[Q[l]])+c; while(l<r&&Work(Q[r],Q[r-1])<Work(i,Q[r])) r--; //维护队列单调性 Q[++r]=i; } printf("%lld",f[n]); }
关于不知足单调性时的特殊状况,下面来一个例题。
$BZOJ2726$(慎重声明,这一题和$Luogu$上的不同)
数据范围:
$[1, 4] 0<N<=1000,0<=S<=2^8,0<=Ti<=2^8,0<=Fi<=2^8$ $[5, 12] 0<N<=300000,0<=S<=2^8,0<=Ti<=2^8,0<=Fi<=2^8$ $[13, 20] 0<N<=100000,0<=S<=2^8,-(2^8)<=Ti<=2^8,0<=Fi<=2^8$
如下的$F[i]$,$T[i]$表示相应数组的前缀和
由于咱们在转移中须要前面分红的批数,因此有一个极为直接的状态:$f[i][j]$表示前$i$个,分为$j$组的答案。
但实际上空间上彻底不行(你$100000$怎么开二维……)
考虑一维的状态,实际上这里用上里一个叫“费用提早计算”的思想。(先把方程写出来吧)
$f[i]=min(f[j]+T[i](F[i]-F[j])+S(F[n]-F[j]))$
考虑这个$S$会对什么产生影响:显然是$j+1~i$的任务产生影响,所以咱们将它提出来计算。
然鹅这个怎么看都不是一个好的方法。(你$100000$怎么跑这个……)
用斜率优化考虑转移,式子能够变形为:
$f[j]=(S+T[i])*F[j]+f[i]-T[i]F[i]-SF[n]$
设$f[j]$为$y$,$F[j]$为$x$,但咱们在转移时就傻眼了,由于这一题的特殊性出题人的毒瘤性,$T[i]$可能为负
即咱们的每次转移时那个固定的斜率不单调,咱们不能将队头的点删掉!
可是至少咱们的$F[i]$有单调性,加入的点有单调性。
这时咱们能够不删除队列中的点,利用二分在队列中找最适合转移的点,再进行转移。
代码应该会感受有些奇怪,主要是智障太懒了,直接在弱化版上魔改了……
#include<bits/stdc++.h> using namespace std; #define int long long inline int read() { int f=1,w=0;char x=0; while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();} while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();} return w*f; } const int N=1000010; int n,s,T[N],F[N],q[N],top=1,f[N]; inline bool check(int j,int i) { if(j<top) return f[q[j+1]]-f[q[j]]<=(T[i]+s)*(F[q[j+1]]-F[q[j]]); else return 0; } inline bool Check(int j,int i) { int a=(f[i]-f[q[j]])*(F[q[j]]-F[q[j-1]]); int b=(f[q[j]]-f[q[j-1]])*(F[i]-F[q[j]]); return a<=b; } main(){ #ifndef ONLINE_JUDGE freopen("Text1.in","r",stdin); #endif n=read(),s=read(); for(int i=1;i<=n;i++) T[i]=read(),F[i]=read(),T[i]+=T[i-1],F[i]+=F[i-1]; for(int i=1;i<=n;i++) { int L=1,R=top; while(L<R) { int mid=(L+R)>>1; if(L==R) break ; if(check(mid,i)) L=mid+1; else R=mid; } int j=q[L]; f[i]=f[j]+T[i]*F[i]+s*F[n]-F[j]*(s+T[i]); while(top>=2&&Check(top,i)) q[top--]=0; q[++top]=i; } printf("%lld",f[n]); }
因此咱们作个总结,斜率优化$DP$适用于状态转移方程为: $f[i]=min(f[j]+S(i,j))+k$($k$为常数)且$S(i,j)$计算时有$(i)*(j)$的部分。 同时咱们设的$x,k$一定要有单调性,若是$k$没有,就不删点二分,若是都没有,就用$CDQ$(至关因而动态插点,动态查找)固然没人拦你用平衡树……
听说……这道题被许多人嘲讽为水题,可我不这么想啊(果然是由于我太弱了吗……
但考场上就真的要……$WOC$你给我解释一下$O_{(mt)}$都能过是什么状况啊!!
你肯定你真的不是用脚在造数据?!
正解(你$™$别给老子想什么暴力卡常):斜率优化$DP$,推方程彻底不难,设$f[i]$为最后乘编号为$i$的车的最小烦躁值,转移方程为: $$ f[i]=min(f[j]+A*(p_i-q_j)^2+B*(p_i-q_j)+C) $$
决策点为$(q_i,f[j]+Aq_j^2-Bq_j)$,当$j$比$k$更优时,知足: $$ \frac{f[j]-f[k]+A*(q_j^2-q_k^2)-B*(q_j-q_k)}{q_j-q_k}<2Ap_i $$ 可是,咱们转移时要知足$p_i>=q_j,y_j=x_i$,因此咱们不能像之前同样维护一个凸包,从全部的决策中转移。
仔细思考一下,咱们的决策来自部分知足条件的前面已经作出的决策,不妨咱们对决策按$i$到达的位置分个组。
即在每一个节点处维护一个凸包(这样必定知足单调性,不解释了),凸包中的点为$f[j]$($j$为目的地为该节点的列车编号),这样咱们转移时就能够知足空间限制了。
再考虑时间限制怎么作,咱们能够枚举时间,再开一个等待队列,存在$q[i]$时到的列车$i$,但他们不能被利用,由于还没枚举到他们的到达时间,而后枚举到时间$t$时,将等待队列中全部到达时间为$t$的列车加进相应的凸包中(并维护凸包的单调性),说明他们能够被利用来转移。
最后在每次转移后将该次转移加入等待队列中,判断是否到达$n$,若是到达,就更新答案(记得加上$q_i$)
给泥萌看我丑陋的代码:
#include<bits/stdc++.h> using namespace std; #define int long long #define S(x) ((x)*(x)) inline int read() { int f=1,w=0;char x=0; while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();} while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();} return w*f; } const int N=200010,M=1001; queue<int> res[M]; int n,m,A,B,C,MaxT,ans=1e18; vector<int> Tbg[M],Q[N]; int q[N],p[N],x[N],y[N],head[N],f[N]; inline double K(int j,int k) { return (double)(f[j]-f[k]+A*(S(q[j])-S(q[k]))-B*(q[j]-q[k]))/(double)(q[j]-q[k]); } main(){ #ifndef ONLINE_JUDGE //freopen("A.in","r",stdin);//Ans=94; freopen("B.in","r",stdin);//Ans=34; #endif n=read(),m=read(),A=read(),B=read(),C=read(); for(int i=1;i<=m;i++) { x[i]=read(),y[i]=read(),p[i]=read(); q[i]=read(),Tbg[p[i]].push_back(i); MaxT=max(MaxT,q[i]); } Q[1].push_back(0); for(int t=0;t<=MaxT;t++) { while(!res[t].empty()) { int pos=y[res[t].front()]; while(Q[pos].size()-head[pos]>=2) { int len=Q[pos].size(); if(K(Q[pos][len-1],Q[pos][len-2])<K(Q[pos][len-2],res[t].front())) break; Q[pos].pop_back(); } Q[pos].push_back(res[t].front()),res[t].pop(); } for(int i=0;i<(int)Tbg[t].size();i++) if((int)Q[x[Tbg[t][i]]].size()>head[x[Tbg[t][i]]]) { int id=Tbg[t][i],pos=x[id]; while((int)Q[pos].size()-head[pos]>=2) { if(K(Q[pos][head[pos]],Q[pos][head[pos]+1])>2.0*A*p[id]) break ; head[pos]++; } int j=Q[pos][head[pos]]; f[id]=f[j]+A*S(p[id]-q[j])+B*(p[id]-q[j])+C; res[q[id]].push(id);if(y[id]==n) ans=min(ans,f[id]+q[id]); } } printf("%lld",ans); }