@atcoder - AGC035D@ Add and Remove


@description@

給定 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。二叉樹

@solution@

考慮最樸素的暴力:枚舉排列,表示卡片被取走的順序,而後算貢獻。
顯然不夠優秀。搜索

注意到對於按序排放的卡片 a b c,假如 b 不被取走,則 a, c 之間取的順序並不重要。
這意味着咱們重複枚舉了不少結果同樣的狀態。

一個 Ai 貢獻次數 = 左邊第一個比它後取走的卡片貢獻次數 + 右邊第一個比它後取走的卡片貢獻次數。咱們老是認爲第 1 張卡片與第 N 張卡片是最後取走的,且貢獻次數爲 1。

考慮一種基於笛卡爾樹的貢獻計算方法:
從根開始向下遞歸,同時維護該子樹 左邊第一個比它後取走的卡片貢獻次數 與 右邊第一個比它後取走的卡片貢獻次數。那麼就能夠算出每一個結點的貢獻次數。

那麼我在搜索的時候能夠一邊枚舉笛卡爾樹的形態一邊計算貢獻。
這樣總搜索量 = 16 個點組成的二叉樹數量 = 第 16 個卡特蘭數 = 35357670。能夠經過該題目。
固然你能夠用記憶化搜索。不過沒有必要,並且 map 常數大,hash 反而麻煩了。

@accepted code@

#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]);
}

@details@

我連暴搜都不會了.jpg。

感受用笛卡爾樹理解起來更爲直觀,並且也更容易證實複雜度。

相關文章
相關標籤/搜索