1. 程式人生 > 實用技巧 >金題大戰Vol.0 D、 二叉搜尋樹

金題大戰Vol.0 D、 二叉搜尋樹

金題大戰Vol.0 D、 二叉搜尋樹

題目描述

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

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

輸入格式

第一行一個整數\(n\),第二行\(n\)個整數\(x1-xn\)

輸出格式

一行一個整數表示答案。

樣例

樣例輸入

5
8 2 1 4 3

樣例輸出

35

資料範圍與提示

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

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

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

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

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

分析

我們可以這樣想,在一個二叉搜尋樹中,一個節點左兒子的權值一定小於該節點的權值,一個節點右兒子的權值一定大於該節點的權值

因此,我們可以把連續的一段區間看成一個子樹,列舉區間中的節點作為根節點

我們發現這就是一個區間\(DP\)

狀態轉移方程為 \(f[l][r]=f[l][k-1]+f[k+1][r]+sum[r]-sum[l-1]\)

用四邊形不等式可以優化到\(n^2\)

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e3+5;
typedef long long ll;
ll f[maxn][maxn],sum[maxn];
int g[maxn][maxn],a[maxn];
inline int read(){
	register int x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
int main(){
	freopen("D.in","r",stdin);
	freopen("D.out","w",stdout);
	register int n;
	n=read();
	for(register int i=1;i<=n;i++){
		a[i]=read();
		sum[i]=sum[i-1]+a[i];
		g[i][i]=i;
		f[i][i]=a[i];
	}
	for(register int d=2;d<=n;d++){
		for(register int l=1;l<=n-d+1;l++){
			register int r=l+d-1;
			f[l][r]=0x3f3f3f3f3f3f3f3f;
			for(register 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;
}