決策單調性最佳化

DeepSeaSpray發表於2024-07-07

決策單調性最佳化

對於最最佳化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\)

\[p_i = \lceil -h_i + \max\{f_j(i)\}\rceil \]

本質上 \(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) + (s(i)-s(j)-1-L)^P \]

打表發現其具有決策單調性,證明略過。

由於 \(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;
}

相關文章