瞎谈区间dp

石子合并

P1880 [NOI1995] 石子合并 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

区间dp模板题

题目描述

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

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

输入格式

数据的第 1 行是正整数 N,表示有 N 堆石子。

第 22 行有 NN 个整数,第 i 个整数 ai​ 表示第 i 堆石子的个数。

输出格式

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

那么很显然,这是一个区间dp的问题

既然是区间dp,那么肯定要定义数组(水字数

因为这很显然是一个环,普通的存储方式肯定覆盖不了所有情况(例:4 3 2三堆合并)

所以这就要用到区间dp(不止)一个很重要(划掉)的思想——破环为链

具体来说就是把这个数组在原数组后re存储一遍

    num[i+n]=num[i];  

这样就可以实现从后往前进行dp

解决了存储问题,那么如何求每次合并石子的得分呢

显然有一种方法就是把每次的得分num[i]都加起来

但这样很显然时间复杂度很大,而且写起来很麻烦(

那么我们可以用前缀和相减来快速求这个区间内的得分

inline int d(int i,int j){return s[j]-s[i-1];}  

下一步就是求dp方程了

定义一个p为合并的石子堆数,i为起始位置,j为终止位置

设置一个k为断点,代表在从i起的第k堆石子为分界点,合并这两堆石子,将k从i到j循环一遍,这样就可以求出合并这堆石子的最小(最大)值了

               f1[i][j] = max(f1[i][j], f1[i][k]+f1[k+1][j]+d(i,j));   
                f2[i][j] = min(f2[i][j], f2[i][k]+f2[k+1][j]+d(i,j));  

最后将合并n堆石子的得分从1到2*n循环一遍,就可以求出最小(最大)值了

那么下面是完整代码

#include<bits/stdc++.h> 
using namespace std;  
const int N=1e11; 
int n,minx,maxx,f1[300][300],f2[300][300],num[300];  
int s[300];  
inline int d(int i,int j){return s[j]-s[i-1];}  
int main()  
{   
    scanf("%d",&n);  
    for(int i=1;i<=2*n;i++) 
    {  
        scanf("%d",&num[i]);  
        num[i+n]=num[i];  
        s[i]=s[i-1]+num[i];  
    }  
    for(int p=1;p<n;p++)  
    {  
        for(int i=1,j=i+p;(j<2*n) && (i<2*n);i++,j=i+p)  
        {  
            f2[i][j]=N;  
            for(int k=i;k<j;k++)  
            {  
                f1[i][j] = max(f1[i][j], f1[i][k]+f1[k+1][j]+d(i,j));   
                f2[i][j] = min(f2[i][j], f2[i][k]+f2[k+1][j]+d(i,j));  
            }  
        }  
    }  
    minx=N;  
    for(int i=1;i<=n;i++)  
    {  
        maxx=max(maxx,f1[i][i+n-1]);  
        minx=min(minx,f2[i][i+n-1]);  
    }  
    printf("%d\n%d",minx,maxx);  
    return 0;  
}

封面是本人的洛谷头像(

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值