二叉搜尋樹 [四邊形不等式優化區間dp]

Vocanda發表於2020-08-15

二叉搜尋樹 [四邊形不等式優化區間dp]

題目描述

\(n\) 個結點,第 \(i\) 個結點的權值為 \(i\)

你需要對它們進行一些操作並維護一些資訊,因此,你需要對它們建立一棵二叉搜尋樹。在整個操作過程中,第i個點需要被操作 \(x_i\) 次,每次你需要從根結點一路走到第 \(i\) 個點,耗時為經過的結點數。最小化你的總耗時。

輸入格式

第一行一個整數 \(n\) ,第二行 \(n\) 個整數 \(x_1\to x_n\)

輸出格式

一行一個整數表示答案。

樣例

樣例輸入

5
8 2 1 4 3

樣例輸出

35

資料範圍與提示

對於 \(10\%\) 的資料,\(n\leqslant 10\)

對於 \(40\%\) 的資料,\(n\leqslant 300\)

對於 \(70\%\) 的資料,\(n\leqslant 2000\)

對於 \(100\%\) 的資料,\(n\leqslant 5000,1\leqslant x_i\leqslant 10^9\)

提示:二叉搜尋樹或者是一棵空樹,或者是具有下列性質的二叉樹:若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;它的左、右子樹也分別為二叉搜尋樹。

分析

第一眼看上去發現自己不會建二叉排序樹了,直接寫暴力……,仔細想一想,二叉排序樹的性質就是左子樹都比根小,右子樹都比根大,那麼就可以把大區間 \([L,R]\) 通過根節點 \(k\) 分成兩部分,然後這裡就是一個幾乎是裸的區間 \(dp\) 的板子了。

在更新 \(f[i][j]\) 的時候,因為 \([i,j]\) 這一段之間的每個節點都要多經過一個節點,所以要加上整棵樹的值,而這用字首和就可以維護。完結!

程式碼

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
using namespace std;
#define ll long long
const int maxn = 5e3+10;
ll f[maxn][maxn];
int g[maxn][maxn];
ll sum[maxn];
int a[maxn];
inline ll read(){//快讀
	ll s = 0,f = 1;
	char ch = getchar();
	while(!isdigit(ch)){if(ch == '-')f = -1;ch = getchar();}
	while(isdigit(ch)){s=s*10+ch-'0';ch = getchar();}
	return s * f;
}
int main(){
	freopen("D.in","r",stdin);
	freopen("D.out","w",stdout);
	ll n = read();
	for(re int i = 1;i<=n;++i){//預處理
		a[i] = read();
		f[i][i] = a[i];//記錄值
		g[i][i] = i;//記錄決策點(四邊形不等式優化)
		sum[i] = sum[i-1] + a[i];//字首和
	}
	//以下是區間dp
	for(re int l = n-1;l >= 1;--l){
		for(re int r = l+1;r <= n;++r){
			f[l][r] = 0x3f3f3f3f3f3f3f3f;//初始化極大值
			for(int k=g[l][r-1];k<=g[l+1][r];++k){
				if(f[l][r] > f[l][k-1] + f[k+1][r] + sum[r]-sum[l-1]){
					f[l][r] = f[l][k-1] + f[k+1][r] + sum[r]-sum[l-1];//向上更新答案
					g[l][r] = k;//記錄決策點
				}
			}
		}
	}
	printf("%lld\n",f[1][n]);//輸出最後的答案
	return 0;
}

相關文章