[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;
}