合并类动态规划,石子归并,合并石子解题报告

石子归并问题

1:任意版

有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为将的一堆石子的数量。设计一个算法,将这N堆石子合并成一堆的总花费最小(或最大)。

此类问题比较简单,就是哈夫曼编码的变形,用贪心算法即可求得最优解。即每次选两堆最少的,合并成新的一堆,直到只剩一堆为止。证明过程可以参考哈夫曼的证明过程。

2.链式归并

问题描述

设有N堆沙子排成一排,其编号为1,2,3,…,N(N<=100)。每堆沙子有一定的数量。现要将N堆沙子并成为一堆。归并的过程只能每次将相邻的两堆沙子堆成一堆,这样经过N-1次归并后成为一堆。找出一种合理的归并方法,使总的代价最小。

例如:有3堆沙子,数量分别为13,7,8,有两种合并方案,

第一种方案:先合并1,2号堆,合并后的新堆沙子数量为20,本次合并代价为20,再拿新堆与第3堆沙子合并,合并后的沙子数量为28,本次合并代价为28,将3堆沙子合并到一起的总代价为第一次合并代价20加上第二次合并代价28,即48;

第二种方案:先合并2,3号堆,合并后的新堆沙子数量为15,本次合并代价为15,再拿新堆与第1堆沙子合并,合并后的沙子数量为28,本次合并代价为28,将3堆沙子合并到一起的总代价为第一次合并代价15加上第二次合并代价28,即43;

采用第二种方案可取得最小总代价,值为43。

【输入格式】

输入由若干行组成,第一行有一个整数,n(1≤n≤100);表示沙子堆数。第2至m+1行是每堆沙子的数量。

【输出格式】

一个整数,归并的最小代价。

【输入样例】

输入文件名:shizi.in

7

13

7

8

16

21

4

18

【输出样例】

输出文件名:shizi.out

239



矩阵连乘与这类问题非常相似。矩阵连乘每次也是合并相邻两个矩阵(只是计算方式不同)。那么石子合并问题可用矩阵连乘的方法来解决。

那么最优子结构是什么呢?如果有N堆,第一次操作肯定是从n-1个对中选取一对进行合并,第二次从n-2对中选取一对进行合并,以此类推……

求出w的前缀和s,s[0]设为0,s[i]表示w[1]+w[2]+...+w[i],这样的话w[i]+w[i+1]+...+w[j]就是s[j]-s[i-1].

f[i][j]表示从第i堆合并到第j堆的最小代价,则f[i][j] = min(f[i][k]+f[k+1][j]+s[j]-s[i-1])(i<=k<j),f[i][j]初始设置为

INT_MAX.

三层循环,最外层枚举一串合并的长度len,最小是2堆石子合并,最大是n堆.

然后枚举i,那么j就是i+len-1,在i和j之间枚举破点k,比较.

DP 过程:

阶段:以归并石子的长度为阶段,一共有n-1个阶段。

状态:每个阶段有多少堆石子要归并,当归并长度为2时,有n-1个状态;

当归并长度为3时,有n-2个状态;

当归并长度为n时,有1个状态。

决策:当归并长度为2时,有1个决策;当归并长度为3时,有2个决策;

当归并长度为n时,有n-1个决策。

#include <iostream>
using namespace std;
#define M 101
#define INF 1000000000
int n,f[M][M],sum[M][M],stone[M];
int main()
{
    int i,j,k,t;
    cin>>n;
    for(i=1;i<=n;i++)
        scanf("%d",&stone[i]);

    for(i=1;i<=n;i++)
    {
        f[i][i]=0;
        sum[i][i]=stone[i];
        for(j=i+1;j<=n;j++)
            sum[i][j]=sum[i][j-1]+stone[j];
    }

    for(int len=2;len<=n;len++)//归并的石子长度
    {
        for(i=1;i<=n-len+1;i++)//i为起点,j为终点
        {
            j=i+len-1;       //由于len,表示有len个石子进行归并,从i开始,由于只有相邻的石子才能合并,所以结束位置j=i+len-1
            f[i][j]=INF;    //初始值都置为无穷大
            for(k=i;k<=j-1;k++)  //中间断开位置,查找在[i,j]什么位置设置断点k,f[i][j]取最优值
            {
                if(f[i][j]>f[i][k]+f[k+1][j]+sum[i][j])
                    f[i][j]=f[i][k]+f[k+1][j]+sum[i][j];
            }
        }
    }
    printf("%d/n",f[1][n]);
    return 0;
}

三:圆形版

如果石子是排成圆形,其余条件不变,那么最优值又是什么呢?

【题目描述】

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

【输入格式】

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

【输出格式】

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

【样例输入】

4

4 4 5 9

【样例输出】

43

54

【来源】

hzoi

因为圆形是首尾相接的,初一想,似乎与直线排列完全成了两个不同的问题。因为每次合并后我们都要考虑最后一个与第一个的合并关系。直线版的最优子结构不见了。f(i, j)表示i—>j合并的最优值似乎并不可行,因为我们可以得到的最优值第一步就是第一个与最后一个合并,那么f(i, j)并不能表示这种关系。

修改一下,f(i, j)表示从第i个开始,合并后面j个得到的最优值。sum(i, j)表示从第i个开始直到i+j个的数量和。那么这个问题就得到解决了。注意要把其看成环形,即在有限域内的合并。

递推公式如下:

#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;

#define  MAXN 100
int sum[MAXN];
int mins[MAXN][MAXN], maxs[MAXN][MAXN];
int INT_MAX=999999999;
int n, stone[MAXN];

int sums(int i, int j) {
	if(i + j >= n) {
		return sums(i, n - i - 1) + sums(0, (i + j) % n);
	} else {
		return sum[i + j] - (i > 0 ? sum[i - 1] : 0);
	}
}

void getBest(int& minnum, int& maxnum) {
	//初始化,没有合并,花费为0
	for(int i = 0; i < n; ++i) {
		mins[i][0] = maxs[i][0] = 0;
	}
	//还需进行合并次数
	for(int j = 1; j < n; ++j) {
		for(int i = 0; i < n; ++i) {
			mins[i][j] = INT_MAX;
			maxs[i][j] = 0;
			for(int k = 0; k < j; ++k) {
	mins[i][j] = min(mins[i][k] + mins[(i + k + 1)%n][j - k - 1] + sums(i, j), mins[i][j]);
	maxs[i][j] = max(maxs[i][k] + maxs[(i + k + 1)%n][j - k - 1] + sums(i, j), maxs[i][j]);

			}
		}
	}
	minnum = mins[0][n - 1];
	maxnum = maxs[0][n - 1];
	for(int i = 0; i < n; ++i) {
		minnum = min(minnum, mins[i][n - 1]);
		maxnum = max(maxnum, maxs[i][n - 1]);
	}

}

int main() {
	scanf("%d", &n);
	for(int i = 0; i < n; ++i)
		scanf("%d", &stone[i]);

	sum[0] = stone[0];
	for(int i = 1; i < n; ++i) {
		sum[i] = sum[i - 1] + stone[i];
	}
	int minnum, maxnum;
	getBest(minnum, maxnum);
	printf("%d/n%d/n", minnum, maxnum);
	return 0;
}

环形DP解题思路2:

对于线性的合并石子问题,dp模型类似于“加括号”那类型的dp题目,设 f(i, j)为 将第i项到第j项合并得到的最优解

关键是,这题目是环形的。环形结构,经常采用双倍长度线性化手段,也就是说,把环形结构看成是长度为环的两倍的线性结构来处理。

环的长度是N,所以题目相当于有一排石子1....N+1....N,然后就可以用线性的石子合并问题的方法做了。

有个要注意的地方,f(i, j) 总是与 f(N +i, N +j) 相等的,所以可以减少一些不必要的计算。

此题的关键在于化环为线性结构,与N个数围成一圈,连续多少个数的最大和,异曲同工。

将N结构的线性表,转换为双倍长度的2N结构的线性表,然后在2N长度的表中,截取我们需要的长度为N的部分就可以了。

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <string>
#include <limits.h>
using namespace std;
int dpx[1100][110],p[1100][1100],s[1100],dp[1100][1100];
int anx,any;
int main()
{
   int n;
   while(cin>>n)
   {
       memset(p,0,sizeof(p));
       for(int i=1;i<=n;i++)
       {
           cin>>s[i];
           s[n+i]=s[i];
       }
       for(int i=1;i<=2*n;i++)
        {
            p[i][i]=s[i];
            for(int j=i+1;j<=2*n;j++)
                p[i][j]=p[i][j-1]+s[j];
        }
       memset(dp,0,sizeof(dp));
       for(int r=1;r<n;r++)     //r 表示待合并进来的石子个数
       {
           for(int i=1;i<=2*n-r;i++) //i表示合并石子,起始位置
           {
               int j=r+i;      //j表示要合并r个石子,合并序列结束的位置
               dpx[i][j]=INT_MAX;  //初始化
               for(int k=i;k<j;k++)
               {
                   dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+p[i][j]);
                   dpx[i][j]=min(dpx[i][j],dpx[i][k]+dpx[k+1][j]+p[i][j]);
               }

           }
       }
       anx=0;
       any=INT_MAX;
       for(int i=1;i<=n;i++) //2N堆中,求解所有长度为N的连续序列合并最大,最小值
       {
          anx=max(anx,dp[i][n+i-1]);
          any=min(any,dpx[i][n+i-1]);

       }
       printf("%d\n",any);
       printf("%d\n",anx);
   }
   return 0;
}
时间: 2024-10-16 09:33:02

合并类动态规划,石子归并,合并石子解题报告的相关文章

合并类动态规划——石子合并

题目描述 Description 在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分.试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分. 输入输出格式 Input/output 输入格式:数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.输出格式:输出共2行,第1行为最小得分,第2行为最大得分. 输入输出样例 Sample input/out

合并类动态规划——能量项链

能量项链 题目描述 Description 在Mars星球上,每个Mars人都随身佩带着一串能量项链.在项链上有N颗能量珠.能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数.并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记.因为只有这样,通过吸盘(吸盘是Mars人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量.如果前一颗能量珠的头标记为m,尾标记为r,后一颗能量珠的头标记为r,尾标记为n,则聚合后释放的能量为m*r*n(

石子合并的动态规划问题

题目大概都是这样的: 设有N堆沙子排成一排,其编号为1,2,3,-,N(N<=300).每堆沙子有一定的数量,可以用一个整数来描述,现在要将这N堆沙子合并成为一堆,每次只能合并相邻的两堆,合并的代价为这两堆沙子的数量之和,合并后与这两堆沙子相邻的沙子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同,如有4堆沙子分别为 1  3  5  2 我们可以先合并1.2堆,代价为4,得到4 5 2 又合并 1,2堆,代价为9,得到9 2 ,再合并得到11,总代价为4+9+11=24,如果第二步

Codevs_2102_石子归并2_(划分型动态规划)

描述 http://codevs.cn/problem/2102/ 与Codevs_1048_石子归并_(划分型动态规划)相比,现在是环状的,起点任意. 2102 石子归并 2 时间限制: 10 s 空间限制: 256000 KB 题目等级 : 黄金 Gold 题目描述 Description 在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分.试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最

动态规划中的石子归并问题

一.有N堆石子,每堆的重量是w[i],可以任意选两堆合并,每次合并的花费为w[i]+w[j],问把所有石子合并成为一堆后的最小花费是多少.因为是可以任意合并,所以每次合并的时候选最小的两堆合并,贪心即可. 二.有N堆石子,每堆的重量是a[i],排成一条直线,每次只能合并相邻的两堆,直到合成一堆为止,问最后的最小花费是多少.分析:因为规定了只能合并相邻的两堆,显然不能使用贪心法.分成子问题来考虑,定义dp[i][j]表示从第i的石子合并到第j个石子的最小花费,那么dp[1][N]就是问题的解.可以

石子归并---区间型动态规划

题目描述 Description 有n堆石子排成一列,每堆石子有一个重量w[i], 每次合并可以合并相邻的两堆石子,一次合并的代价为两堆石子的重量和w[i]+w[i+1].问安排怎样的合并顺序,能够使得总合并代价达到最小. 输入描述 Input Description 第一行一个整数n(n<=100) 第二行n个整数w1,w2...wn  (wi <= 100) 输出描述 Output Description 一个整数表示最小合并代价 样例输入 Sample Input 4 4 1 1 4 样

3002 石子归并 3

时间限制: 1 s 空间限制: 256000 KB 题目等级 : 钻石 Diamond 题解 查看运行结果 题目描述 Description 有n堆石子排成一列,每堆石子有一个重量w[i], 每次合并可以合并相邻的两堆石子,一次合并的代价为两堆石子的重量和w[i]+w[i+1].问安排怎样的合并顺序,能够使得总合并代价达到最小. 输入描述 Input Description 第一行一个整数n(n<=3000) 第二行n个整数w1,w2...wn  (wi <= 3000) 输出描述 Outpu

codevs 2102 石子归并2

传送门 2102 石子归并 2 时间限制: 10 s 空间限制: 256000 KB 题目等级 : 黄金 Gold 题目描述 Description 在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分.试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分. 输入描述 Input Description 数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的

51Nod - 1021 石子归并

51Nod -  1021 石子归并 N堆石子摆成一条线.现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价.计算将N堆石子合并成一堆的最小代价. 例如: 1 2 3 4,有不少合并方法 1 2 3 4 => 3 3 4(3) => 6 4(9) => 10(19) 1 2 3 4 => 1 5 4(5) => 1 9(14) => 10(24) 1 2 3 4 => 1 2 7(7) => 3 7