P1880 [NOI1995] 石子合并


原题链接

P1880
AC记录:Accepted

题目大意

在一个圆形操场的四周摆放 N N N堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N N N堆石子合并成 1 1 1堆的最小得分和最大得分。

输入格式

数据的第 1 1 1行是正整数 N N N,表示有 N N N堆石子。
2 2 2行有 N N N个整数,第 i i i个整数 a i a_i ai表示第 i i i堆石子的个数。

输出格式

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

S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input

4
4 5 9 4

S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output

43
54

H i n t & E x p l a i n \mathbf{Hint\&Explain} Hint&Explain
最小价值:
44合并,得分8,石子变成8 5 9
85合并,得分13,石子变成13 9
139合并,得分22,石子变成22
总得分8+13+22=43
最大价值:
59合并,得分14,石子变成4 14 4
414合并,得分18,石子变成18 4
184合并,得分22,石子变成22
总得分14+18+22=54

数据范围

对于 100 % 100\% 100%的数据, 1 ≤ n ≤ 100 , 1 < a i < 20 1≤n≤100,1<a_i<20 1n100,1<ai<20

解题思路

此题为区间dp。
先从普通版本来入手。
假如说这些石头是排成一列的,那么我们就可以枚举其中的一段区间求答案。
f i , j f_{i,j} fi,j为合并第 i i i堆石子到第 j j j堆石子的最小得分, s u m i , j sum_{i,j} sumi,j为第 i i i堆石子到第 j j j堆石子的石子个数,由于 [ i , j ] [i,j] [i,j]这一段区间是由两个小区间合并得来的,则我们可以在这个区间内枚举一个断点,把这个区间分成两份。按照题目中的说法,我们就可以得出状态转移方程:
f i , j = { 0 i = j f i , k + f k + 1 , j + s u m i , j i < j , i ≤ k < j f_{i,j}=\begin{cases} 0&i=j \\ f_{i,k}+f_{k+1,j}+sum_{i,j}&i<j,i\le k<j \end{cases} fi,j={0fi,k+fk+1,j+sumi,ji=ji<j,ik<j
✒  注意 由于大区间是由小区间合并得来的,所以在枚举区间的 时候要从小区间枚举到大区间。 { \def\fcol{#448AFF} \def\scol{#ECF3FF} \def\sidelength{50pt} \def\titlehigh{30pt} \def\titlewordhigh{37pt} \color{\fcol}{\rule[0pt]{2pt}{\sidelength}} \color{\scol}{\rule[\titlehigh]{200pt}{20pt}} \kern{-200pt} \color{#FFFFFF}{\rule[0pt]{200pt}{\titlehigh}} \color{\fcol}{\raisebox{\titlewordhigh}{\kern{-195pt}{\footnotesize\bf ✒ 注意}}} \color{black} % 在raisebox{ /here/ }{}修改文字高度,底线为7pt,依此减少10pt {\raisebox{17pt}{\kern{-195pt}{\footnotesize\bf 由于大区间是由小区间合并得来的,所以在枚举区间的}}} {\raisebox{7pt}{\kern{-195pt}{\footnotesize\bf 时候要从小区间枚举到大区间。}}} }  

上代码

#include<bits/stdc++.h>

using namespace std;

#define fu(i,st,ed) for(int i=st; i<=ed; i++)
#define fd(i,st,ed) for(int i=st; i>=ed; i--)

int     f1[210][210];
int     f2[210][210];
int     sum[210];
int     a[210];
int     i,j,l;
int     n;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    /* Code */
    cin>>n;
    fu(i,1,n)
        cin>>a[i],a[i+n]=a[i];
    fu(i,1,2*n)
        sum[i]=sum[i-1]+a[i];
    fu(i,2,n)
        fu(l,1,2*n-i+1)
        {
            int r=l+i-1;
            f1[l][r]=0;
            f2[l][r]=2147483647;
            fu(j,l,r-1)
            {
                f1[l][r]=max(f1[l][r],f1[l][j]+f1[j+1][r]+sum[r]-sum[l-1]);
                f2[l][r]=min(f2[l][r],f2[l][j]+f2[j+1][r]+sum[r]-sum[l-1]);
            }
        }
    int tar1=0,tar2=2147483647;
    fu(l,1,n+1)
    {
        int r=l+n-1;
        tar1=max(tar1,f1[l][r]);
        tar2=min(tar2,f2[l][r]);
    }
    cout<<tar2<<endl<<tar1<<endl;
    return 0;
}

完美切题 ∼ \sim

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值