給定 N 張排成一行的卡片,第 i 張卡片上面寫着 Ai。code
重複如下操做,直到只剩下兩張卡片。
取出卡片 i,將卡片 i 左邊的卡片與卡片 i 右邊的卡片的 A 加上 Ai。遞歸
求最後剩下的兩張卡片的 A 的可能的最小和。ip
Constraints
2≤N≤18, 0≤Ai≤10^9(1≤i≤N)hash
Input
輸入格式以下:
N
A1 A2 ... ANit
Output
輸出最小和。io
Sample Input 1
4
3 1 4 2
Sample Output 1
16class
先選 1 獲得 4 5 2,再選此時的 5 獲得 9 7,最小和 9 + 7 = 16。二叉樹
考慮最樸素的暴力:枚舉排列,表示卡片被取走的順序,而後算貢獻。
顯然不夠優秀。搜索
注意到對於按序排放的卡片 a b c,假如 b 不被取走,則 a, c 之間取的順序並不重要。
這意味着咱們重複枚舉了不少結果同樣的狀態。
一個 Ai 貢獻次數 = 左邊第一個比它後取走的卡片貢獻次數 + 右邊第一個比它後取走的卡片貢獻次數。咱們老是認爲第 1 張卡片與第 N 張卡片是最後取走的,且貢獻次數爲 1。
考慮一種基於笛卡爾樹的貢獻計算方法:
從根開始向下遞歸,同時維護該子樹 左邊第一個比它後取走的卡片貢獻次數 與 右邊第一個比它後取走的卡片貢獻次數。那麼就能夠算出每一個結點的貢獻次數。
那麼我在搜索的時候能夠一邊枚舉笛卡爾樹的形態一邊計算貢獻。
這樣總搜索量 = 16 個點組成的二叉樹數量 = 第 16 個卡特蘭數 = 35357670。能夠經過該題目。
固然你能夠用記憶化搜索。不過沒有必要,並且 map 常數大,hash 反而麻煩了。
#include <cstdio> #include <algorithm> using namespace std; typedef long long ll; const int MAXN = 20; const ll INF = (1LL<<60); int N; ll A[MAXN + 5]; ll dfs(int l, int r, int cntl, int cntr) { if( l + 1 == r ) return 0; ll ret = dfs(l, l+1, cntl, cntl+cntr) + dfs(l+1, r, cntl+cntr, cntr) + (cntl+cntr)*A[l+1]; for(int i=l+2;i<=r-1;i++) ret = min(ret, dfs(l, i, cntl, cntl+cntr) + dfs(i, r, cntl+cntr, cntr) + (cntl+cntr)*A[i]); return ret; } int main() { scanf("%d", &N); for(int i=1;i<=N;i++) scanf("%lld", &A[i]); printf("%lld\n", dfs(1, N, 1, 1) + A[1] + A[N]); }
我連暴搜都不會了.jpg。
感受用笛卡爾樹理解起來更爲直觀,並且也更容易證實複雜度。