将n(1≤n≤200)堆石子绕圆形操场摆放,现要将石子有次序地合并成一堆。
 规定每次只能选相邻的两堆石子合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。 
 (1)选择一种合并石子的方案,使得做n-1次合并,得分的总和最小。 
 (2)选择一种合并石子的方案,使得做n-1次合并,得分的总和最大
 输入
 4
 4 5 9 4
 输出
 43
 54
线性结构是N个数据元素以有序的方式排列。访问线性结构一般采用由前至后的遍历方法。
 线性动态规划就是在线性数据的基础上,通过某种递推方式(状态转移方程)得到最终结构的一种规划算法。
 sum[i]: 从第1堆到第i堆石子数总和
 fmax[i][j]: 从第i堆石子合并到第j堆石子的最大得分
 fmin[i][j]: 从第i堆石子合并到第j堆石子的最小得分
初始化: fmax[i][i] = 0, fmin[i][i]= INF
 状态方程:
 fmax[i][j] = max{fmax[i][k]+fmax[k+1][j]+sum[j]-sum[i-1]} i <= k < j
 fmin[i][j] = min{fmin[i][k]+fmin[k+1][j]+sum[j]-sum[i-1]} i <= k < j
由于题中围成一个环,我们将这条链再延长一倍,变成2*n堆,地中第i堆与第n+i堆相同,
 动态规划求解后,答案为f(1,n), f(2,n+1), ... , f(n-1,2*n-2)中的最优解
状态转移
 要计算f(i,j)的值时需知道所有f(i,k)和f(k+1,j)的值,
 以len=j-i+1作为DP 的区间长度,从小到大枚举len,
 然后枚举i的值,根据len和i用公式计算出j的值,然后枚举k,时间复杂度为O(n^3)
/*  https://loj.ac/problem/10147  */
 #include <iostream>
 using namespace std;
const int MAXN = 201;
 const int INF = 0x3f3f3f3f;
 int arr[2*MAXN];
 int sum[2*MAXN];
 int fmax[2*MAXN][2*MAXN];
 int fmin[2*MAXN][2*MAXN];
int main()
 {
     int i, j, k, n, len;
     cin >> n;
     for (i = 1; i <= n; ++i)
     {
         cin >> arr[i];
         arr[n+i] = arr[i]; 
     }
     for (i = 1; i <=(n<<1); ++i)
         sum[i] = sum[i-1] + arr[i];
    for (len = 2; len <= n; ++len)
         for (i = 1; i <= (n<<1)-len+1; ++i)
         {
             j = i + len - 1;
             // 初始化
             fmax[i][j] = 0;
             fmin[i][j] = INF;
             for (k = i; k < j; ++k)
             {
                 fmax[i][j] = max(fmax[i][j], fmax[i][k] + fmax[k+1][j] + sum[j] - sum[i-1]);
                 fmin[i][j] = min(fmin[i][j], fmin[i][k] + fmin[k+1][j] + sum[j] - sum[i-1]);
             }
         }
    int ansmax = 0, ansmin = INF;
     for (i = 1; i < n; ++i)
     {
         ansmax = max(ansmax, fmax[i][i+n-1]);
         ansmin = min(ansmin, fmin[i][i+n-1]);
     }
cout << ansmin << endl << ansmax << endl;
     return 0;
 }
 四边形不等式优化请参考
https://oi-wiki.org/dp/opt/quadrangle/
https://www.cnblogs.com/a1b3c7d9/p/10984353.html
dp[i][j]=min{dp[i][k]+dp[k+1][j]+w[i][j]} (i≤k<j)
 把dp[i][k]+dp[k+1][j]取得最值的那个k, 称为dp[i][j]的最优决策点。

 
#include <iostream>
 using namespace std;
 const int MAXN = 201;
 const int INF = 0x3f3f3f3f;
 int arr[2*MAXN];
 int sum[2*MAXN];
 int fmax[2*MAXN][2*MAXN];
 int fmin[2*MAXN][2*MAXN];
 int ma[2*MAXN][2*MAXN]; //ma[i][j]: 从第i堆石子合并到第j堆石子的最大得分时的最优决策点
 int mi[2*MAXN][2*MAXN]; //mi[i][j]: 从第i堆石子合并到第j堆石子的最小得分时的最优决策点
int main()
 {
     int i, j, k, n, len, t;
     cin >> n;
     for (i = 1; i <= n; ++i)
     {
         cin >> arr[i];
         arr[n+i] = arr[i];
     }
     for (i = 1; i <=(n<<1); ++i)
     {
         sum[i] = sum[i-1] + arr[i];
         ma[i][i] = i;
         mi[i][i] = i;
     }
    for (len = 2; len <= n; ++len)
         for (i = 1; i <= (n<<1)-len+1; ++i)
         {
             j = i + len - 1;
             // 初始化
             fmax[i][j] = 0;
             fmin[i][j] = INF;
             // 四边形不等式优化
             for (k = ma[i][j-1]; k <= ma[i+1][j] && k < j; ++k)
             {
                 t = fmax[i][k] + fmax[k+1][j] + sum[j] - sum[i-1];
                 if (fmax[i][j] < t)
                 {
                     fmax[i][j] = t;
                     ma[i][j] = k;
                 }
             }
            for (k = mi[i][j-1]; k <= mi[i+1][j] && k < j; ++k)
             {
                 t = fmin[i][k] + fmin[k+1][j] + sum[j] - sum[i-1];
                 if (fmin[i][j] > t)
                 {
                     fmin[i][j] = t;
                     mi[i][j] = k;
                 }
             }
         }
    int ansmax = 0, ansmin = INF;
     for (i = 1; i < n; ++i)
     {
         ansmax = max(ansmax, fmax[i][i+n-1]);
         ansmin = min(ansmin, fmin[i][i+n-1]);
     }
cout << ansmin << endl << ansmax << endl;
     return 0;
 }
  



















