P1880 [NOI1995]石子合并

本文深入探讨区间动态规划(DP),通过经典石子合并问题,解析如何设定状态和推导状态转移方程,实现求解区间内最优值的目标。文章详细介绍了区间DP的模板,展示了如何通过优化时间复杂度,利用前缀和方法计算合并成本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

大佬 蒟蒻来啦!!

这次还是给大家讲解一下DP
有的人可能会问同样是DP,那么这次有什么不同呢?(注意:这篇不仅仅是题解!!)。其实答案很简单,上一篇那到最长不下降子序列是一道线性动态规划的经典模板题,也就是最简单易懂的动态规划(其实类比单链表和双链表)。那么这次我们来看看区间DP,区间DP,其实就是求一个区间内的最优值从而达到求整体最优的效果。一般这种题目,在设置状态的时候,都可以设f[i][j]为区间i-j的最优值 而f[i][j]的最优值,这有两个小区间合并而来的,为了划分这两个更小的区间,我们则需用用一个循环变量k来枚举,而一般的状态转移方程便是:
f[i][j]=max/min(f[i][j],f[i][k]+f[k][j]+something)
f[i][j]=max/min(f[i][j],f[i][k]+f[k][j]+something)

我们则需要根据这个题目的实际含义进行变通即可.
而区间dp的大致模板是:

for (int len=2;len<=n;len++)
    for (int i=1;i+len-1<=n;i++)
    {
        int j=i+len-1;
        for (int k=i;k<=j;k++)
            f[i][j]=max/min(f[i][j],f[i][k]+f[k][j]+something)
    }

那讲到这里就很自然(一点都不自然)的引入了今天我们要看的一道题,刚才我已经说了最长不下降子序列是线性DP的模板题,那么今天说的这道石子合并就是区间DP的经典模板。下面来讲解一下我们先看这道题的题目描述以及输入输出数据点。(如下)

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

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

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

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

输入输出样例
输入 #1 复制
4
4 5 9 4
输出 #1 复制
43
54

那么上面就是这道题目的内容,即每次进行合并的时候就是合并一个区间.

我们可以设f[i][j]为合并i-j区间的石子的最优方案即最小花费,假设在我们进行i和j的状态转移的时候必然更小的区间已经求出了最优的方案数,那么我们只需要用k去划分石子数去进行状态转移即可.
因为本来的时间是O(n^3),我们则需要优化时间,采用前缀和的方法,把合并的数量进行计算即可.
我们可以得到状态转移方程:

f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+cost[i][j])

表示两堆石子的合并花费加上新的花费得到的结果.
在代码实现上,我们需要注意:len是从2开始的,诸如f[1][1],f[2][2]都是不需要花费价值的,即初始值0;状态转移方程是f[i][k]+f[k+1][j]+cost[i][j]) 而不是f[i][k]+f[k][j]+cost[i][j])合并的是两堆不同的石子而不是一堆,这点需要注意.
代码如下:

#include<cstdio>
#include<iostream>
using namespace std;
int a[210],f1[210][210],f2[210][210];
int main()
{
    int n,Max,Min;
    cin>>n;
    for(int i=1;i<=n;i++)
	{
        scanf("%d",&a[i]);
        a[n+i]=a[i];
    }
    for(int i=1;i<=2*n;i++)
	{
		a[i]=a[i]+a[i-1];
	}
    for(int r=2;r<=n;r++)
	{
        for(int i=1;i<=2*n-r+1;i++)
		{
            int j=i+r-1;
            f1[i][j]=f1[i+1][j]+a[j]-a[i-1];
            f2[i][j]=f2[i+1][j]+a[j]-a[i-1];
            for(int k=i;k<j;k++)
			{
                f1[i][j]=max(f1[i][j],f1[i][k]+f1[k+1][j]+a[j]-a[i-1]);
                f2[i][j]=min(f2[i][j],f2[i][k]+f2[k+1][j]+a[j]-a[i-1]);
            }
        }
    }
    Max=f1[1][n];
	Min=f2[1][n];
    for(int i=2;i<=n;i++)
	{
        if(f1[i][i+n-1]>Max) Max=f1[i][i+n-1];
        if(f2[i][i+n-1]<Min) Min=f2[i][i+n-1];
    }
    cout<<Min<<endl<<Max;
    return 0;
}

其实这道题就是这么简单!!希望大家都能理解区间DP
(后面还有更多更难的DP!!!!)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值