CF1768F Wonderful Jump 題解

zifanwang發表於2024-05-08

考慮 dp,記 \(f_i\) 表示跳到位置 \(i\) 的最小代價,考慮哪些狀態會對當前狀態有貢獻。

考慮狀態 \(f_j\) 對當前狀態有貢獻需要滿足什麼條件,發現如果存在 \(j<k<i\) 滿足 \(a_k=\min(a_j,a_{j+1},\dots,a_i)\),則先跳到 \(k\) 再跳到 \(i\) 會更優。可得 \(\min(a_j,a_{j+1},\dots,a_i)\in\{a_j,a_i\}\)

考慮對 \(i-j\) 進行根號分治,當 \(i-j\le \sqrt n\) 時,直接暴力更新即可。當 \(i-j>\sqrt n\) 時,因為代價不會超過 \(n(i-j)\)(每次跳一個),所以 \(\min(a_j,a_{j+1},\dots,a_i)<\sqrt n\)

\(\min(a_j,a_{j+1},\dots,a_i)=a_j/a_i\) 兩種情況考慮即可。對於等於 \(a_i\) 的情況,發現 \(a_i<\sqrt n\),直接暴力列舉。對於等於 \(a_j\) 的情況,初始 \(j\) 為左邊第一個滿足 \(a_j\le a_i\) 的位置,每次跳到左邊第一個小於當前 \(a_j\) 的位置更新即可。

時間複雜度 \(\mathcal O(n\sqrt n)\)

參考程式碼:

#include<bits/stdc++.h>
#define ll long long
#define mxn 400003
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
int n,t,ls,q[mxn],a[mxn],l1[mxn],l2[mxn],l3[mxn];
ll dp[mxn];
signed main(){
	scanf("%d",&n);
	const int b=sqrt(n);
	rep(i,1,n)scanf("%d",&a[i]);
	rep(i,1,n){
		while(t&&a[q[t]]>a[i])t--;
		l1[i]=q[t];
		q[++t]=i;
	}
	t=0;
	rep(i,1,n){
		while(t&&a[q[t]]>=a[i])t--;
		l2[i]=q[t];
		q[++t]=i;
	}
	rep(i,1,n){
		if(a[i]<=b)ls=i;
		l3[i]=ls;
	}
	rep(i,2,n){
		dp[i]=n*(i-1ll);
		int mn=a[i];
		drep(j,i-1,max(1,i-b)){
			mn=min(mn,a[j]);
			dp[i]=min(dp[i],dp[j]+(ll)(i-j)*(i-j)*mn);
		}
		int k=l3[l1[i]];
		if(a[i]<=b){
			drep(j,i-1,1){
				if(a[j]<=a[i])break;
				dp[i]=min(dp[i],dp[j]+(ll)(i-j)*(i-j)*a[i]);
			}
		}
		while(k){
			dp[i]=min(dp[i],dp[k]+(ll)(i-k)*(i-k)*a[k]);
			k=l2[k];
		}
	}
	rep(i,1,n)cout<<dp[i]<<' ';
	return 0;
}