题目:https://www.luogu.org/problem/P1880
Description:
n堆石子摆成一圈,现在要合并成一堆,每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。计算出合并的最大分和最小分。
Solution:
区间dp,分阶段划分问题。由于石子围成了环,现在把环转化为两倍长的链。变成 (2 * n - 1) 堆,其中第 i 堆与第(i + n) 堆相同,用动态规划求解后,取dp[1][n], dp[2][n+1] ,... ,dp[i][n+i -1] 中的最优值。
如果环是这样:1 2 3 4
转化后的链:1 2 3 4 3 2 1
转化后就可以往一个方向进行dp
区间 DP 的特点:
合并 :即将两个或多个部分进行整合,当然也可以反过来;
特征 :能将问题分解为能两两合并的形式;
求解 :对整个问题设最优值,枚举合并点,将问题分解为左右两个部分,最后合并两个部分的最优值得到原问题的最优值。
Code:
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int n;
int sum[300], a[300];
int dp1[300][300];//max
int dp2[300][300];//min
void init()
{
memset(sum, 0, sizeof(sum));
memset(dp1, 0, sizeof(dp1));
memset(dp2, INF, sizeof(dp2));
memset(a, 0, sizeof(a));
}
int main()
{
init();
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
a[i + n] = a[i];
dp2[i][i] = 0; //自己与自己合并得分为0
dp2[i+n][i+n] = 0;
}
sum[1] = a[1];
for(int i = 1; i <= 2 * n - 1; i++) sum[i] = sum[i - 1] + a[i];
for(int len = 1; len <= n; len++) //划分区间长度,当区间的长度为len时
{
for(int i = 1; i <= 2 * n - 1; i++)//起点为i //第n个值在最中间,就不用重复了,现在一共2*n-1个数
{
int j = i + len - 1;//终点为j
for(int k = i; k < j && k <= 2 * n - 1 && j <= 2 * n - 1; k++)//分割点为k
{
dp1[i][j] = max(dp1[i][j], dp1[i][k] + dp1[k + 1][j] + sum[j] - sum[i - 1]);
dp2[i][j] = min(dp2[i][j], dp2[i][k] + dp2[k + 1][j] + sum[j] - sum[i - 1]);
}
}
}
int Max = 0, Min = INF;
for(int i = 1; i <= n; i++) //从不同的起点合并n堆的结果
{
Max = max(Max, dp1[i][i + n - 1]);
Min = min(Min, dp2[i][i + n - 1]);
}
cout << Min << endl;
cout << Max << endl;
return 0;
}