關於斜率優化,我就是一個傻子啊,真的一直沒弄懂……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); }