GarsiaWachs算法 - 石子合并(大数据版)

石子合并
总时间限制: 1000ms 内存限制: 128000kB

描述
有N堆石子排成一排(n<=5000),现要将石子有次序地合并成一堆,规定每次只能选相邻的两堆合并成一堆,并将新的一堆的石子数,记为改次合并的得分,编一程序,由文件读入堆数n及每堆石子数(<=100);选择一种合并石子的方案,使得做n-1次合并,得分的总和最少

输入
第一行为石子堆数n
第二行为每堆石子数,每两个数之间用一空格分隔。

输出
输出为一行,为最小的合并得分

样例输入
4
4 5 9 4

样例输出
44

GarsiaWachs算法:专用于石子合并问题。它将时间复杂度从划分DP的O(n^3)降到了O(n^2)。

定义一个石子序列A[0], A[1], A[2], ..., A[n-1],则计算这个石子序列的最小合并数ans的算法如下:
1.(假设存在A[-1]A[n],将它们的值设为无穷大。这个假设只是便于理解,在代码上不用这么写)
2.从-1到n, 找到第一个k使A[k]<=A[k+2],保存这个k
3.定义一个临时变量temp,使temp=A[k]+A[k+1],同时ans+=temp
4.从k-1到-1,找到第一个i使A[i]>temp,保存这个i
5.从表A中同时删去A[k]A[k+1](注意:这个操作不会影响A[0]A[k-1]的部分)
6.在i之后插入一个temp
7.如此,把2~6共循环n-1次,最后ans即为答案

参考代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using std::cin;
using std::cout;
using std::endl;
inline int readIn()
{
    int a;
    scanf("%d",&a);
    return a;
}

int n;
long long ans;
std::vector<int> L;

int Combine()
{
    int k=L.size()-1-1;
    //如果我们在A[0]到A[n-3]找不到A[k]<=A[k+2],那么k的值应该为n-2,即倒数第二个元素。因为我们假设A[n]=+∞
    for(int i=0; i2; i++) //因为要+2,所以在0到n-3中找
    {
        if(L[i]<=L[i+2])
        {
            k=i;
            break; //找第一个k
        }
    }
    int ret=L[k]+L[k+1]; //前文的temp

    L.erase(L.begin()+k);
    L.erase(L.begin()+k); //由于数组已经向左移了一个了,因此之前的A[k+1]跑到了A[k]的位置上,所以还是删除A[k]

    int index=-1; //如果在A[k-1]到A[0]找不到A[i]>temp,我们就在A[-1]后插入temp,因为A[-1]=+∞
    for(int i=k-1; i>=0; i--) //从右往左
    {
        if(L[i]>ret)
        {
            index=i;
            break; //第一个
        }
    }
    L.insert(L.begin()+index+1, ret); //因为是在后面插入,所以要+1
    return ret; //加上这个返回值
}

int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        L.push_back(readIn());
    }

    for(int i=0; i1; i++)
    {
        ans+=Combine();
    }
    printf("%lld",ans);
    return 0;
}

你可能感兴趣的:(OI)