二叉搜尋樹 [四邊形不等式優化區間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;
}