洛谷 P1880 [NOI1995] 石子合并 解题报告

题目描述

在一个圆形操场的四周摆放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

区间DP的版子题,区间DP的方程为f[i][j]=max(f[i][k]+f[k+1][j]+(区间i到j的值))
但这道题稍有不同,所有石子实际上排成了一个环,要区间的DP的话要断环为链,如果依次枚举断点的话,复杂度又要上一个台阶,那怎么办呢
断环为链+倍增
举个例子,如果一个环是1 2 3,在存储时把它倍增,变成1 2 3 1 2 3,这样的话,所有可能的情况都会出现 1 2 3,2 3 1,3 1 2这样就避免了枚举断点的循环

关于区间i-j的值,可以用跑一遍前缀和,查询时就很方便,不过注意要跑1-2*n的前缀和

用记忆化搜索写的,相对于DP,记搜更好写,也更容易排错
用求最小值记搜的来说明
int dfs1(int l,int r)
{
    if(l==r)return f1[l][r]=0;//如果左右断点重合则返回0
    if(f1[l][r])return f1[l][r];//算过直接返回
    int ans=inf;//求最小值的话搜索初值要设为一个较大值
    for(int k=l;k<r;k++)//枚举断点,这里有人可能会问不是只能合并相邻的石子吗,虽然题目里说了只能合并相同的区间,但这样写是等效的,
//合并1-2,2-3的代价和合并1-3的代价是相同的 ans
=min(ans,dfs1(l,k)+dfs1(k+1,r)+sum[r]-sum[l-1]);//套方程不解释 return f1[l][r]=ans;//返回 }

最大值同理

完整代码

#include<bits/stdc++.h>
using namespace std;
const int MAXN=205;
const int inf=0x7fffffff;
int n,sum[MAXN],a[MAXN],f1[MAXN][MAXN],f2[MAXN][MAXN];
int dfs1(int l,int r)
{
    if(l==r)return f1[l][r]=0;
    if(f1[l][r])return f1[l][r];
    int ans=inf;
    for(int k=l;k<r;k++)
       ans=min(ans,dfs1(l,k)+dfs1(k+1,r)+sum[r]-sum[l-1]);
    return f1[l][r]=ans;
}
int dfs2(int l,int r)
{
    if(l==r)return f2[l][r]=0;
    if(f2[l][r])return f2[l][r];    
    int ans=0;
    for(int k=l;k<r;k++)
       ans=max(ans,dfs2(l,k)+dfs2(k+1,r)+sum[r]-sum[l-1]);
    return f2[l][r]=ans;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
       cin>>a[i],a[i+n]=a[i];
    for(int i=1;i<=2*n;i++)   
       sum[i]=sum[i-1]+a[i];
        dfs1(1,2*n);
    dfs2(1,2*n);
    int ans1=inf,ans2=0;
    for(int i=1;i<=n;i++)
       {
        ans1=min(ans1,f1[i][i+n-1]);    
        ans2=max(ans2,f2[i][i+n-1]);    
            }
    cout<<ans1<<endl<<ans2;        
    return 0;
} 

参考大佬@ FFF团的题解



猜你喜欢

转载自www.cnblogs.com/pcpcppc/p/9328456.html