決策單調性最佳化
對於最最佳化DP來說,即決策點具有單調性。
程式碼實現
分治
以 P5503 [JSOI2016] 燈塔 為例。
答案 \(p_i = \max\{\lceil h_j - h_i + \sqrt{|i-j|} \rceil\}\)。
去除絕對值,分到兩種情況中去做,可以先不用考慮上取整,輸出時再做即可。
我們先考慮 \(j \leq i\) 的情況,對於另外一種,將整個陣列翻轉過來即可。
設 \(f_j(i) = h_j + \sqrt{i-j}\)。
把 \(h_i\) 提出來,代入 \(f_j\)。
本質上 \(f\) 是由同一個函式影像平移出來的。
由於遞增容易發現的是每一個函式兩兩之間只有一個交點。
可以自行畫圖理解,決策單調性是比較顯然的。
\(p_i\) 取值與 \(p_j\) 無關,這樣的話我們就可以整體二分。
Solve(l,r,L,R)
表示求值區間 \([l,r]\) 決策區間 \([L,R]\)。
每一次,找到區間中點 \(mid\) 與其決策點 \(MID\),記錄答案後分到兩邊去做。
Solve(l,mid-1,L,MID)
與 Solve(mid+1,r,MID,R)
。
時間複雜度 \(O(n \log n)\)
#include<bits/stdc++.h>
using namespace std;
const int maxn=5*1e5;
int n;
int a[maxn+5];
double p[maxn+5];
inline double Calc(int i,int j){
return a[j]-a[i]+sqrt(i-j);
}
void Solve(int l,int r,int L,int R){
if(l>r) return;
int mid=(r-l)/2+l,k;
double mx=0,val;
for(int i=L;i<=R&&i<=mid;i++){
val=Calc(mid,i);
if(val>=mx) mx=val,k=i;
}
p[mid]=max(p[mid],mx);
Solve(l,mid-1,L,k);
Solve(mid+1,r,k,R);
}
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
Solve(1,n,1,n);
for(int i=1;i<=n/2;i++)
swap(a[i],a[n-i+1]),swap(p[i],p[n-i+1]);
Solve(1,n,1,n);
for(int i=1;i<=n/2;i++)
swap(a[i],a[n-i+1]),swap(p[i],p[n-i+1]);
for(int i=1;i<=n;i++) printf("%d\n",(int)ceil(p[i]));
return 0;
}
單調佇列
以 P1912 [NOI2009] 詩人小G 為例。
令 \(s(i) = \sum_{j=1}^i (len[j]+1)\)。
設 \(f(i)\) 表示考慮到第 \(i\) 句時的答案。
有狀態轉移方程:
打表發現其具有決策單調性,證明略過。
由於 \(f(i)\) 轉移與 \(f(j)\) 有關,所以不能整體二分,可以用單調佇列。
單調佇列儲存決策點。
由於決策單調性,我們可以用二分實現函式 Get(x,y) = k
表示決策 \(x\) 在 \([1,k]\) 中優於 \(y\)。
按照從小到大的順序求 \(f\)。
首先是加入 \(0\),初始時,其是所有 \(f\) 的最優決策點。
接著每一次計算 \(f_i\) 重複以下步驟。
- 當
Get(第一個,第二個) < i
時彈出隊頭,因為它已不優。 - 更新並記錄答案。
- 當
Get(倒數第二個,最後一個) >= Get(最後一個,i)
彈出隊尾,因為其既不優於倒數第二個,也不優於 \(i\)。 - 插入 \(i\)。
應該解釋的比較清楚了。
對於本題,需要注意的是,使用 long double
儲存答案,因為答案可能會很大,並且當答案很大時並不用輸出,不用擔心丟失精度。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ldb long double
const int maxn=2e5;
const int maxs=30;
int n,L,P;
char str[maxn+5][maxs+5];
ll s[maxn+5];
ldb f[maxn+5];
int g[maxn+5];
ll q[maxn+5];
int hd,tl;
inline ldb Fpow(ldb x,ll y){
ldb res=1;
if(x<0) x=-x;
for(;y;y>>=1,x*=x)
if(y&1) res*=x;
return res;
}
inline ldb Calc(int i,int j){
return f[j]+Fpow(s[i]-s[j]-L-1,P);
}
inline int Get(int x,int y){
int l=y,r=n,mid,ans=y-1;
while(l<=r){
mid=(l+r)/2;
if(Calc(mid,x)<=Calc(mid,y)) ans=mid,l=mid+1;
else r=mid-1;
}
return ans;
}
inline void Work(){
scanf("%d%d%d",&n,&L,&P);
for(int i=1;i<=n;i++){
scanf("%s",str[i]);
s[i]=s[i-1]+(ll)strlen(str[i])+1;
g[i]=0,f[i]=1e20;
}
hd=1,tl=0;
q[++tl]=0;
f[0]=0;
for(int i=1;i<=n;i++){
while(hd<tl&&Get(q[hd],q[hd+1])<i) hd++;
g[i]=q[hd],f[i]=Calc(i,q[hd]);
while(hd<tl&&Get(q[tl],i)<=Get(q[tl-1],q[tl])) tl--;
q[++tl]=i;
}
if(f[n]>1e18) puts("Too hard to arrange");
else{
printf("%lld",(ll)f[n]);
hd=1,tl=0;
for(int i=n;i>0;i=g[i]) q[++tl]=i;
q[++tl]=0;
for(;tl>0;tl--){
puts("");
for(int i=q[tl]+1;i<=q[tl-1];i++){
if(i<q[tl-1]) printf("%s ",str[i]);
else printf("%s",str[i]);
}
}
}
puts("--------------------");
}
signed main(){
int T;
scanf("%d",&T);
while(T--) Work();
return 0;
}