[luogu1880] [NOI1995]石子合并

传送门

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;
}
posted @ 2018-10-24 21:54  Neworld1111  阅读(108)  评论(0编辑  收藏  举报