石子归并直线型(DP)

直线型原题地址http://acm.nyist.net/JudgeOnline/problem.php?pid=737
基本的题意:有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。

  1. 设dp[i][j]是从第i堆石子到第j堆石子的最优解,即最小值
    2.sum[i][j]是第i堆石子到第j堆石子的总质量
    举个例子:
    1.当只有1堆石子的时候,很明显不用移动石子就已经合并为1堆了。那么dp[i][i]=0;
    2.当有两堆的时候,dp[i][j]就是第1堆和第2堆合起来的总值,即sum[i][j](实质是dp[i][i]+dp[j][j]+sum[i][j]);
    关键来了,当有3堆石子的时候,由于石子每次都是1堆1堆合并起来的,那么3堆的石子的最后解必然是由两堆石子合起来的,但是将三堆石子分成两堆显然有2种方法,(比如说最开始有 4 2 6 三堆,那么最后的一堆必然是由6 6或者 4 8合并而来,这时候就必然会产一个大小的问题,即最终dp[1][3]=min(dp[1][1]+dp[2][3],dp[1][2]+dp[3][3])+sum[1][3])
    由此可以得出转移公式
    这里写图片描述
    解释一下第二行,第二行的意思是依次枚举k,然后取这之中dp[i][k]+dp[k+1][j]+sum[i][j]的最小值
    最后给上代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#define inf 0x3f3f3f3f
using namespace std;
int a[50005];
int dp[505][505];
int sum[505];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        memset(sum,0,sizeof(sum));
        memset(dp,inf,sizeof(dp));
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            dp[i][i]=0;
            sum[i]+=a[i]+sum[i-1];
        }

        for(int l=1; l<=n-1; l++)//l是合并的次数

        {
            for(int i=1; i<=n-l; i++)//i是在当前合并次数下区间的首地址(此地址非指针的那个地址,可以理解为数组下标)
            {
                int j=i+l;
                for(int k=1; k<=j-1; k++)//j是相应的末地址

                {
                    dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
                }
            }
        }
        printf("%d\n",dp[1][n]);
    }
    return 0;
}

最后说明一下:本次所给的代码复杂度是O(n^3),是最基础的,然后题还可以优化成O(n^2),这其中用到了四边形优化,由于博主水平有限,还没有理解就不具体阐述这其中的原理了,故只给出经过优化后的O(n^2)代码,最后还有一种nlogn的写法,实在水平有限,想了解的同学可以自行百度
给出一个参考的四边形优化的博客http://blog.csdn.net/u014800748/article/details/45750737

#include <iostream>
#include <stdio.h>
#include <string.h>
#define inf 0x3f3f3f3f
using namespace std;
int a[50005];
int dp[505][505],s[505][505];
int sum[505];
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        memset(sum,0,sizeof(sum));
        memset(dp,inf,sizeof(dp));
        memset(s,0,sizeof(s));
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            dp[i][i]=0;
            s[i][i]=i;
            sum[i]+=a[i]+sum[i-1];
        }
        for(int l=1; l<=n-1; l++)//l是合并的次数
        {
            for(int i=1; i<=n-l; i++)//i是在当前合并次数下区间的首地址
            {
                int j=i+l;//j是相应的末地址
                for(int k=s[i][j-1]; k<=s[i+1][j]; k++)
                {
                    int temp=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
                    if(temp<dp[i][j])
                    {
                        dp[i][j]=temp;
                        s[i][j]=k;
                    }
                }
            }
        }
        printf("%d\n",dp[1][n]);
    }
    return 0;
}

石子归并还有一个环形例题,等下次有时间了再写吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值