• [NOI1995]石子合并


    题目

    Description

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

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

    Input

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

    第 2 行有 N 个整数,第 i 个整数 a_i 表示第 堆石子的个数。

    Output

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

    Sample Input

    4
    4 5 9 4

    Sample Output

    43
    54

    思路

    有一道很类似的题:

    最小代价树

    首先这是一道经典的dp题;

    那么d[i][j]表示i-j的最小代价,sum[i][j]是i-j每个石头的代价;

    答案就是dp[1][n]了;

    那么每一次合并i-j就需要加上i-j的权值代价;

    dp[1][3]=min(dp[1][1]+dp[2][3]+sum[1][3],dp[1][2]+dp[3][3]+sum[1][3]);

    所以我们需要枚举断开的位置k,i-j的合并就是i-k的合并加上k+1-j的合并再加上代价;

    所以转移方程就是:

    dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);

    (sum是前缀和;)

     

    然后枚举len长度,i起点,k断开的位置;

    为什么不是先枚举i起点,在枚举j终点呢?

    for(ll i=1;i<=n;i++)

    for(ll j=i+1;j<=n;j++)

    for(ll k=i;k<j;k++)

    假如求出dp[1][2],然后枚举到dp[1][3]=dp[1][1]+dp[2][3] ;

    然鹅dp[2][3]还未求出,所以我们需要枚举区间长度;

    从区间长度小枚举到区间长度大,这样算长度大的区间时,访问小区间,小区间就有答案;

    所以要枚举len长度,i起点,k断开的位置;

     

    那么求最大值则反之;

     

    代码

    #include<bits/stdc++.h>
    typedef long long ll;
    using namespace std;
    inline ll read()
    {
        ll a=0,f=1; char c=getchar();
        while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
        while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
        return a*f;
    }
    ll n;
    ll dp[2001][2001],f[2001][2001];
    ll a[2001],s[2001];
    int main()
    {
        n=read();
        for(ll i=1;i<=n;i++)
        {
            a[i]=read();
            a[i+n]=a[i];//破环成列 
        }
        for(ll i=1;i<=n*2;i++)//计算前缀
            s[i]=s[i-1]+a[i];
        for(ll len=2;len<=2*n;len++)//枚举区间长度,len=1没什么意义额
        for(ll i=1;i<=2*n-len+1;i++)
        {
            ll j=i+len-1;
            for(ll k=i;k<j;k++)
                dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+s[j]-s[i-1]);//转移方程
            f[i][j]=1<<30;//因为答案是求最小值,但是数组一开始都为0,答案也会为0,所以我们需要统计时改为最大值
            for(ll k=i;k<j;k++)
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);//转移方程
        }
        ll ans=0;
        for(ll i=1;i<=n;i++)//因为是环所以要枚举每个n长度的区间 
            ans=max(ans,dp[i][i+n-1]);
        ll answer=1<<30;
        for(ll i=1;i<=n;i++)
            answer=min(answer,f[i][i+n-1]);
        printf("%lld
    %lld
    ",answer,ans);
    }
  • 相关阅读:
    python基础之列表的坑
    python基础之字典篇
    坦克大战[源码] 你懂得
    java例程练习(键盘事件)
    android基础(对话框风格Activity实现)
    android基础(Activity)
    android基础(开发环境搭建)
    android基础(android程序的后台运行问题)
    java(敲 七)
    java例程练习(匿名类用法)
  • 原文地址:https://www.cnblogs.com/wzx-RS-STHN/p/13416047.html
Copyright © 2020-2023  润新知