洛谷 P1880 [NOI1995]石子合并

题目描述

在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

输入输出格式

输入格式:

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

 

输出格式: 

输出共2行,第1行为最小得分,第2行为最大得分.

思路分析

一、贪心

贪心只能处理“任取两堆”,而不能处理“相邻两堆”。任取两堆的题目就是 合并果子。

二、划分阶段

本题是区间DP的模板题(然而对蒟蒻还是很难)

很容易想到用f[i][j]表示i~j的最大(小)得分。

三、状态转移

在i~j这一区间内,枚举下标k进行拆分。拆分区间是区间DP的重要思想之一。

合并i~k与k+1~j的区间,会加上i~j之间所有石子个数之和。

故我们得到状态转移方程为f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]) (最小值max改为min)

其中s为前缀和(不懂的自觉退役……自觉补习)。

四、环

dalao们可能会使用高级数据结构,但其实有一种更简单的方法:把原数组往后复制一遍。

以样例4 5 9 4为例,复制后得到:

4 5 9 4 4 5 9 4

i~j长度不能超过n

五、玄学40分

基础DP的代码比较简短,但麻烦的地方有很多,关键在于环。

for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=2*n;j++)
        {
            if(j-i>=n)
                break;
            for(int k=i;k<j;k++)
            {
                ma[i][j]=max(ma[i][j],ma[i][k]+ma[k+1][j]+sum[j]-sum[i-1]);
                mi[i][j]=min(mi[i][j],mi[i][k]+mi[k+1][j]+sum[j]-sum[i-1]);
            }
        }
    }

这是之前的一份错误代码,它使用直接计算长度的方式来保证长度<=n。

但是这样的转移顺序会出现问题。如,外层循环i=1,第二层j=10,内层循环k=4时:

f[1][10]=max(f[1][10],f[1][4]+f[5][10]+s[10]-s[0])

可以发现f[5][10]还未转移,这也是玄学40分的原因。

 

解决方法倒是有不少,这里 借 鉴 了其他题解,用r=i+j-1来记录右边界,使r和i,j直接联系,完美避免了上述情况。

详情见代码。

七、代码

码风丑陋勿喷,没有优化勿喷。

另外,以此题的数据范围完全用不到四边形不等式这类的高档算法。

以后如果想起来,还会再来更新O(n2)算法(咕)

#include<iostream>
#include<iomanip>
#include<cstring>
using namespace std;
int n,a[205],mi[205][205],ma[205][205],sum[205],ansmax,ansmin,r;
int main()
{
    memset(mi,0x3f,sizeof(mi));
    memset(ma,0,sizeof(ma));
    //初始化
    cin>>n;
    sum[0]=0;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        a[i+n]=a[i];
    //输入
    }
    for(int i=1;i<2*n;i++)
    {
        sum[i]=sum[i-1]+a[i];
        mi[i][i]=ma[i][i]=0;
        //维护前缀和,再次初始化数组
    }
    for(int i=2;i<=n;i++)
    {
        for(int j=1;i+j-1<2*n;j++)
        {
            r=i+j-1;
            for(int k=j;k<r;k++)
            {
                ma[j][r]=max(ma[j][r],ma[j][k]+ma[k+1][r]+sum[r]-sum[j-1]);
                mi[j][r]=min(mi[j][r],mi[j][k]+mi[k+1][r]+sum[r]-sum[j-1]);//状态转移
            }
        }
    }
    ansmin=0x7fffffff;
    ansmax=-1;
    for(int i=1;i<=n;i++)
    {
        ansmin=min(ansmin,mi[i][i+n-1]);
        ansmax=max(ansmax,ma[i][i+n-1]);
    //选择最大(小)值
    }
    cout<<ansmin<<"\n"<<ansmax;
    return 0;
}

 

posted @ 2018-10-16 22:50  _wkjzyc  阅读(313)  评论(0编辑  收藏  举报