环形结构上的石子合并

题目:
在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N堆石子合并成 1堆的最大得分。
输入格式
数据的第 1行是正整数 N,表示有 N 堆石子。
第 2行有 N 个整数,第 i 个整数 ai​ 表示第 i 堆石子的个数。
输出格式
1 行为最大得分
输入样例
7
13 7 8 16 21 4 18
输出样例
376

算法思想:
环形结构上的动态规划问题
在许多环形结构的问题中,我们都能够通过枚举法,选择一个位置把环断开,变成
线性结构进行计算,最后根据每次枚举的结果求出答案。我们把能通过上述枚举方式求解环形问题称为“可拆解的环形问题”,这也是本文章的主要研究对象。我们的目标是采取适当的策略避免枚举,从而使时间复杂度得到降低。
第一种策略:
执行两侧DP,第一次在任意位置把环断开成链,按照线性问题求解;第二次通过适当的条件和赋值,保证计算出的状态等价于断开位置强制相连。
第二种策略:在任意位置断开成链,然后复制一倍接在末尾。这是解决环形结构DP常用手法。

假设石头为a1,a2,a3…an,首尾相连之后就是 a1,a2,a3…an,a1,a2,a3…an;序列长度变为原来的2倍。

a1,a2,a3…an,a1,a2,a3…an;
这样和原来的线性石子合并一样了,枚举区间长度2<= len<=n,枚举区间起点,这个有点区别,枚举范围是1<= i <=2n
dp[ i ][ j ]=max(dp[ i ][ j ],dp[ i ][ k ]+dp[k+1][ j ]+(sum[ j ]-sum[ i-1 ]));

求区间终点 int j=i+len-1; if(j>n*2) break ; 越界结束

然后枚举环装中的每一个数为起点,长度为n的区间,求最大值即可。

for(int i=1;i<=n;i++)//枚举环状序列的起点,长度为n
MAX=max(dp[i][i+n-1],MAX);//求最大值

完整代码:

#include
using namespace std;
const int maxn=450;//区间长度为2*n
int dp[maxn][maxn];
int sum[maxn];//前缀和数组
int a[maxn];//每堆石子的个数
int main()
{
    int n,x;
    sum[0]=0;
    while(scanf("%d",&n)!=EOF){
        memset(dp,0,sizeof(dp));
        memset(sum,0,sizeof(sum));
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            sum[i]=sum[i-1]+a[i];//预处理
            dp[i][i]=0;
        }
 
        int in=1;//首尾相连之后
        for(int i=n+1;i<=2*n;i++)
            sum[i]+=(sum[i-1]+a[in++]);
 
        for(int len=2;len<=n;len++)//枚举区间长度
        {
            for(int i=1;i<=2*n;i++)//枚举区间起点
            {
                int j=i+len-1;//区间终点
                if(j>n*2) break;//越界结束
                for(int k=i;k<j;k++)//枚举分割点,构造状态转移方程
                {
                    dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+(sum[j]-sum[i-1]));
                }
            }
        }
        int MAX=-1;
        for(int i=1;i<=n;i++)//枚举环状序列的起点,长度为n
        MAX=max(dp[i][i+n-1],MAX);//求最大值
        printf("%d\n",MAX);
    }
    return 0;
}

你可能感兴趣的:(动态规划)