DP - 区间DP - 石子合并 + 环形石子合并

DP - 区间DP - 石子合并 + 环形石子合并

文章目录

    • DP - 区间DP - 石子合并 + 环形石子合并
      • 1、石子合并
      • 2、环形石子合并

1、石子合并

设有N堆石子排成一排,其编号为1,2,3,…,N。

每堆石子有一定的质量,可以用一个整数来描述,现在要将这N堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。

例如有4堆石子分别为 1 3 5 2, 我们可以先合并1、2堆,代价为4,得到4 5 2, 又合并 1,2堆,代价为9,得到9 2 ,再合并得到11,总代价为4+9+11=24;

如果第二步是先合并2,3堆,则代价为7,得到4 7,最后一次合并代价为11,总代价为4+7+11=22。

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

输入格式
第一行一个数N表示石子的堆数N。

第二行N个数,表示每堆石子的质量(均不超过1000)。

输出格式
输出一个整数,表示最小代价。

数据范围
1≤N≤300

输入样例:
4
1 3 5 2
输出样例:
22

分析:

由 于 每 次 合 并 的 两 堆 石 子 是 相 邻 的 , 可 以 考 虑 用 动 态 规 划 来 求 解 。 由于每次合并的两堆石子是相邻的,可以考虑用动态规划来求解。

状 态 表 示 : f [ i ] [ j ] : 合 并 区 间 [ i , j ] 内 的 石 子 所 需 的 最 小 花 费 。 状态表示:f[i][j]:合并区间[i,j]内的石子所需的最小花费。 f[i][j]:[i,j]

状 态 计 算 : 由 于 每 次 我 们 都 是 将 相 邻 两 堆 合 并 成 一 堆 , 因 此 可 以 考 虑 枚 举 两 堆 的 分 界 点 。 长 度 为 l e n 的 区 间 [ l , r ] 共 有 l e n − 1 种 划 分 ( l e n − 1 个 不 同 的 分 界 点 ) , 取 这 些 划 分 种 的 最 小 值 即 可 。 即 f [ l ] [ r ] = m i n ( f [ l ] [ k ] + f [ k + 1 ] [ r ] + s u m [ l , r ] ) , k ∈ [ l , r − 1 ] 。 s u m [ l , r ] 是 区 间 [ l , r ] 石 子 重 量 的 总 和 , 可 以 通 过 前 缀 和 来 预 处 理 。 状态计算:\\由于每次我们都是将相邻两堆合并成一堆,因此可以考虑枚举两堆的分界点。\\长度为len的区间[l,r]共有len-1种划分(len-1个不同的分界点),取这些划分种的最小值即可。\\即f[l][r]=min(f[l][k]+f[k+1][r]+sum[l,r]),k∈[l,r-1]。\\sum[l,r]是区间[l,r]石子重量的总和,可以通过前缀和来预处理。 len[l,r]len1(len1)f[l][r]=min(f[l][k]+f[k+1][r]+sum[l,r])k[l,r1]sum[l,r][l,r]

注意: 求 最 小 值 时 , 初 始 化 置 为 ∞ 。 求最小值时,初始化置为∞。

代码:

#include
#include

using namespace std;

const int N=310;

int n,f[N][N],s[N];

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>s[i];
    
    for(int i=1;i<=n;i++) s[i]+=s[i-1];
    
    for(int len=2;len<=n;len++)
        for(int l=1;l+len-1<=n;l++)
        {
            int r=l+len-1;
            f[l][r]=1e9;
            for(int k=l;k<r;k++)
                f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
        }
        
    cout<<f[1][n]<<endl;
    return 0;
}

2、环形石子合并

将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。

规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。

请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:

选择一种合并石子的方案,使得做 n−1 次合并得分总和最大。
选择一种合并石子的方案,使得做 n−1 次合并得分总和最小。
输入格式
第一行包含整数 n,表示共有 n 堆石子。

第二行包含 n 个整数,分别表示每堆石子的数量。

输出格式
输出共两行:

第一行为合并得分总和最小值,

第二行为合并得分总和最大值。

数据范围
1≤n≤200

输入样例:
4
4 5 9 4
输出样例:
43
54

分析:

是 第 一 题 的 “ 环 ” 形 版 本 , 将 “ 环 ” 转 化 成 “ 链 ” 再 用 第 一 题 的 解 法 即 可 。 是第一题的“环”形版本,将“环”转化成“链”再用第一题的解法即可。

将 需 要 合 并 的 序 列 复 制 一 遍 再 枚 举 左 端 点 从 1 到 n , 就 转 化 成 链 了 。 维 护 一 个 长 度 为 n 的 区 间 即 可 。 将需要合并的序列复制一遍再枚举左端点从1到n,就转化成链了。维护一个长度为n的区间即可。 1nn

注意:

求 最 小 值 先 初 始 化 为 正 无 穷 , 求 最 大 值 先 初 始 化 为 负 无 穷 , 但 别 忘 了 处 理 长 度 为 1 的 情 况 代 价 为 0 。 求最小值先初始化为正无穷,求最大值先初始化为负无穷,\\但别忘了处理长度为1的情况代价为0。 10

代码:

#include
#include
#include

#define inf 0x3f3f3f3f

using namespace std;

const int N=410;

int n,f[N][N],g[N][N],w[N],s[N];

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>w[i];
        w[i+n]=w[i];
    }
     
    for(int i=1;i<=2*n;i++)  s[i]=s[i-1]+w[i];
    
    memset(f,0x3f,sizeof f);
    memset(g,-0x3f,sizeof g);
    
    for(int len=1;len<=n;len++)
        for(int l=1;l+len-1<=2*n;l++)
        {
            int r=l+len-1;
            if(len==1) f[l][r]=g[l][r]=0;
            else
            {
                for(int k=l;k<r;k++)
                {
                    f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
                    g[l][r]=max(g[l][r],g[l][k]+g[k+1][r]+s[r]-s[l-1]);  
                }
            }
        }
    
    int Min=inf,Max=-inf;
    for(int i=1;i<=n;i++) Min=min(Min,f[i][i+n-1]),Max=max(Max,g[i][i+n-1]);
    
    cout<<Min<<endl<<Max<<endl;
    
    return 0;
}

你可能感兴趣的:(DP,算法,动态规划,acm竞赛,dp)