1. 程式人生 > >[luogu1880] [NOI1995]石子合並

[luogu1880] [NOI1995]石子合並

for tps eof pri cpp 產生 urn str pre

傳送門

Solution

看上去的確是一道dp題,還是區間dp的那種

但是你發現這是一個環,非常難受

所以我們想啊:如果這是一條鏈該多好

於是乎,我們直接把這個環拉直。1 2 3

但是怎麽繞回去呢?

再接一遍好了:1 2 3 1 2

這樣就可以了!!

那麽如何進行區間dp呢?

首先我們定義\(dp[i][len]\)為合並\([i,i+len-1]\)這段區間的最優值

那麽我們一定可以找到一個中點\(k\),使得我們先合並\(dp[i][k-i+1]\)\(dp[k+1][i+len-k-1]\),然後我們再通過這兩個區間,合並出\(dp[i][len]\)

那麽石子合並產生的權值如何計算?其實就是\([i,i+len-1]\)

這段區間內的石子數之和,這個我們可以用前綴和維護

綜上所述,\(dp[i][len] = max/min(dp[i][len],dp[i][k-i+1]+dp[k+1][i+len-k-1]+w[i,i+len-1])(k \in [i,i+len-1])\)

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 205
#define INF 2147483647

int dp1[MAXN][MAXN],dp2[MAXN][MAXN];
int Prefix[MAXN];
int a[MAXN];
int N;

int main() {

    scanf("%d",&N);
    std::memset(dp1,0,sizeof(dp1));
    std::memset(dp2,0,sizeof(dp2));
    for(register int i=1;i<(N<<1);++i) {
        for(register int j=2;j<=N;++j) dp2[i][j] = INF;
    }
    Prefix[0] = 0;

    for(register int i=1;i<=N;++i) {
        scanf("%d",&a[i]);
        Prefix[i] = a[i] + Prefix[i-1];
    }

    for(register int i=N+1;i<(N<<1);++i) {
        Prefix[i] = Prefix[i-1] + a[i-N];
    }

    for(register int len=2;len<=N;++len) {
        for(register int i=1;i+len-1<(N<<1);++i) {
            for(register int k=i;k<i+len-1;++k) {
                dp1[i][len] = std::max(dp1[i][len],dp1[i][k-i+1]+dp1[k+1][i+len-k-1]+Prefix[i+len-1]-Prefix[i-1]);
                dp2[i][len] = std::min(dp2[i][len],dp2[i][k-i+1]+dp2[k+1][i+len-k-1]+Prefix[i+len-1]-Prefix[i-1]);
            }
        }
    }

    int ans_max = 0;
    int ans_min = INF;
    for(register int i=1;i<=N;++i) {
        ans_max = std::max(ans_max,dp1[i][N]);
        ans_min = std::min(ans_min,dp2[i][N]);
    }
    printf("%d\n%d\n",ans_min,ans_max);
    return 0;
}

[luogu1880] [NOI1995]石子合並