【題解】【THUSC 2016】成績單 LOJ 2292 區間dp

Prelude

快THUWC了,因此補一下之前的題。
真的是一道神題啊,網上的題解沒幾篇,並且還都看不懂,我作了一天才作出來。ios

傳送到LOJ:(>人<;)spa


Solution

直接切入正題。
咱們考慮區間dp,第一件事是離散化。
而後用\(g(i,j)\)表示消除完閉區間\([i,j]\)的最小費用。
而後呢?怎麼轉移?exm???
這時候會有一個很是天然的想法。
計算\(g(i,j)\)的時候,咱們枚舉兩個數\(l,r\),而後保留下值在閉區間\([l,r]\)以內的全部數,先消除掉其餘的數字,就只剩\([l,r]\)以內的數字了,再一次性消除掉她們。
時間複雜度\(O(n^5)\),可是顯然是錯的。
錯在哪裏呢?大概是錯在下面這種狀況,我懶得構造具體的反例了。
對於一組數字\(abcabca\),咱們能夠先消除掉中間的\(a\),再消除掉\(bcbc\),最後再消除掉\(aa\),在咱們的dp裏面彷佛並無考慮到這種狀況。
由於\(aa\)是最後消除掉的,所以若是咱們選擇保留\(a\)的話,會保留下來全部的\(a\)
咱們太仁慈了,保留下來了\([l,r]\)之間的全部的數字,其實不必定要保留全部數字。
怎麼辦呢?
腦洞大開!
咱們用\(f(i, j, l, r)\)表示,消除完在閉區間\([i,j]\)以內的,除了值在\([l,r]\)之間的全部數字。
注意,在\([l,r]\)之間的數字,能夠消除,也能夠不消除。
而後顯然有這個東西:
code

\(\Large g(i, j) = \min f(i, j, l, r)\)

實際上就是枚舉 \(l,r\)嘛。
而後咱們考慮 \(f(i, j, l, r)\)如何轉移。
當閉區間 \([i,j]\)內元素所有在 \([l,r]\)之間的時候,顯然 \(f(i, j, l, r)=0\)
當閉區間 \([i,j]\)內元素所有不在 \([l,r]\)之間的時候,顯然 \(f(i, j, l, r)=g(i, j)\)
\(f(i, j, l, r)=g(i, j)\)彷佛構成了循環依賴?
那麼,咱們枚舉 \(l,r\)的時候,必須保證區間 \([i,j]\)內存在至少一個數字在 \([l,r]\)內,這樣就不會有循環依賴了。
解決了 \(f(i, j, l, r)\)的邊界問題,接下來看如何轉移。
像普通的區間dp同樣,咱們枚舉區間的分裂點 \(k\),而後把區間 \([i,j]\)分裂成 \([i,k]\)\([k+1,j]\)兩部分,遞歸作下去。
有式子:
\(\Large f(i, j, l, r) = \min f(i, k, l, r) + f(k+1, j, l, r)\)

感覺一下,感受彷佛是能處理各類狀況的?
可是實際上和剛剛的作法沒有任何區別。
由於對於狀態 \(f(i, j, l, r)\),咱們仍然保留了 \([l,r]\)之間的全部數字,仍然是那麼的仁慈。
咱們須要加一種暴力斬掉全部數字的狀況。
有式子:
\(\Large f(i, j, l, r) = \min g(i, k) + f(k+1, j, l, r)\)

仔細感覺一下,這兩個 \(f(i, j, l, r)\)的轉移式結合起來以後,就能夠處理掉全部狀況了!
時間複雜度仍然是 \(O(n^5)\)
實現採用記憶化搜索,效果棒棒噠~
真是一道神題啊。。。


Code

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <iostream>

using namespace std;
const int N = 52;
const int W = 1010;
const int INF = 0x3f3f3f3f;
int _w;

int bmin( int &a, int b ) {
    return a = b < a ? b : a;
}

int n, a, b, w[N];
int vis[W], num[N], m;
int f[N][N][N][N], g[N][N];
int F( int, int, int, int );
int G( int, int );

void discrete() {
    for( int i = 1; i <= n; ++i )
        vis[w[i]] = 1;
    m = 1;
    for( int i = 1; i < W; ++i )
        if( vis[i] )
            vis[i] = m, num[m++] = i;
    --m;
    for( int i = 1; i <= n; ++i )
        w[i] = vis[w[i]];
}

bool contain( int i, int j, int l, int r ) {
    for( int p = i; p <= j; ++p )
        if( w[p] >= l && w[p] <= r )
            return true;
    return false;
}

bool all( int i, int j, int l, int r ) {
    for( int p = i; p <= j; ++p )
        if( w[p] < l || w[p] > r )
            return false;
    return true;
}

int F( int i, int j, int l, int r ) {
    int &now = f[i][j][l][r];
    if( now != -1 ) return now;
    if( all(i, j, l, r) ) return now = 0;
    if( !contain(i, j, l, r) ) return now = G(i, j);
    now = INF;
    for( int k = i; k < j; ++k ) {
        bmin( now, F(i, k, l, r) + F(k+1, j, l, r) );
        bmin( now, G(i, k) + F(k+1, j, l, r) );
    }
    // printf( "f[%d][%d][%d][%d] = %d\n", i, j, l, r, now );
    return now;
}

int G( int i, int j ) {
    int &now = g[i][j];
    if( now != -1 ) return now;
    now = INF;
    for( int l = 1; l <= m; ++l )
        for( int r = l; r <= m; ++r )
            if( contain(i, j, l, r) ) {
                int u = num[l], v = num[r];
                bmin( now, F(i, j, l, r) + (v-u)*(v-u)*b + a );
            }
    // printf( "g[%d][%d] = %d\n", i, j, now );
    return now;
}

int main() {
    cin >> n >> a >> b;
    for( int i = 1; i <= n; ++i )
        cin >> w[i];
    discrete();
    memset(f, -1, sizeof f);
    memset(g, -1, sizeof g);
    printf( "%d\n", G(1, n) );
    return 0;
}
相關文章
相關標籤/搜索