石子合并(动态规划)详细解题报告

合集下载

石子归并(动态规划)

石子归并(动态规划)

石子归并(动态规划)动态规划石子合并问题【石材加固】在一个圆形操场的四周摆放着n堆石子。

现要将石子有次序地合并成一堆。

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

尝试设计一个算法来计算将n堆石头合并成一堆的最小分数和最大分数。

[输入文件]包含两行,第1行是正整数n(1<=n<=100),表示有n堆石子。

第2行有n个数,分别表示每堆石子的个数。

【输出文件】输出两行。

第1行中的数字是最低分数;第2行中的数字是最高分。

[输入示例]44459[输出示例]4354【分析】起初,我以为贪心法可以解决这个问题,但事实上,由于必须有两个相邻的桩合并,贪心法不能保证每次都能得到所有桩中石头数量最多的两个桩。

例如,以下示例:6346542如果使用贪心法计算最小分数,则应为以下合并步骤:第一次合并3465422,3合并分数为5,第二次合并546545,4合并分数为9,第三次合并96545,4合并分数为9,第四次合并9699,6合并分数为15,第五次合并15915,9合并分数为24,总分=5+9+9+15+24=62但是如果采用如下合并方法,却可以得到比上面得分更少的方法:第一次合并3465423,4合并得分是7第二次合并765427,6合并得分是13第三次合并135424,2合并得分是6第四次合并13565,6合并得分是11第五次合并131113,11合并得分是24总得分=7+13+6+11+24=61因此,我们知道这个问题不能用贪婪的方法来解决。

在上面的例子中,相邻的两个石子数量分别为13和11的桩第五次合并。

第一堆、第二堆和第三堆(石块数量分别为3、4和6)以及第四、第五和第六堆(石块数量分别为5、4和2)组合四次后形成两堆石块。

所以问题归结为如何使两个子序列的N-2组合分数之和达到最优。

为了实现这一目标,我们将第一个序列分为两个:第一和第二堆形成子序列1,第三堆形成子序列2。

石子归并一

石子归并一
动态规划
中山纪念中学 宋新波
动态规划的关键点
• 最优化原理 子问题最优化结构 • 无后效性 未来与过去无关 • 状态 描述最优解的结构 • 状态转移方程 递归定义最优解的值 • 程序实现 用记忆化搜索或迭代法求解
例1:01背包
• 有N种物品和一个容量为V的背包。第i种物品 只有1过背包 容量,且价值总和最大,求出这个最大价值。
方法三
• 证明: • 当x=0时:只要都不选就可以; • 当1<=x<=2^k-1时:把x转为二进制,选择对应位为1 的物品。如x=5,5的二进制为101,5=1+4; • 当2^k<=x<=m[i]时:由于m[i]-(2^k-1)<2^k,则选 择m[i]-(2^k-1),剩下的为x-m[i]+2^k-1<=2^k-1可 以由前面的1,2,...,2^(k-1)组合得到。 m[ i ] • 物品i可以被分解为 log 2 个数量为1的物品,题目就 转化为 log 个物品的01背包。 • 时间复杂度为O(V* log )。
方法二
• 方法一中计算f[i]时需要从1到i-1逐一枚举j,我们 可以记录g[x]表示,以数值x结尾的最长上升子序列 的长度,利用树状数组维护g[1..x]的最大值,在计 算f[i]时直接利用树状数组计算出g[1..a[i]-1]的最 大值。 • 如果a[i]数值不大可以直接做,否则需要离散化。 • 时间复杂度为O(nlgn)。
N i 1
方法二
• 把“m[i]个第i种物品”看作是“m[i]种物品”,每种 物品只有一个,体积和价值分别为v[i]和w[i],这样 原问题就转变为 m[i] 个物品的01背包问题。 • 时间复杂度也是O(V* m[i] )。

动态规划-石子合并问题

动态规划-石子合并问题

动态规划-⽯⼦合并问题(1)问题描述 在⼀个圆形操场的四周摆放着 num 堆⽯⼦。

先要将⽯⼦有次序地合并成⼀堆。

规定每次只能选相邻的 2 堆⽯⼦合并成新的⼀堆,并将新的⼀堆⽯⼦数记为该次合并的耗费⼒⽓。

试设计⼀个算法,计算将 n 堆⽯⼦合并成⼀堆的最省⼒⽓数。

(2)算法思想 对于给定的 n 堆⽯⼦,当只有⼀堆时,不⽤搬,进⽽不耗费⼒⽓,然后依次计算出从 2 堆 ~ num 堆⽯⼦的最优解,并且堆数递增求最优解,依赖于上⼀步的解进⾏计算所得;(3)算法思路 此解法和矩阵连乘类似,我们知道矩阵连乘也是每次合并相邻的两个矩阵,那么⽯⼦合并可以⽤矩阵连乘的⽅式来解决。

设 dp[i][j] 表⽰第 i 到第 j 堆⽯⼦合并的最优值,sum[i][j] 表⽰第 i 到第 j 堆⽯⼦的所耗费的⼒⽓总数。

动规⽅程如下:(4)代码展⽰public class StoneMerge {/*** 记录⽯⼦堆的数量*/private static int num;/*** 记录每堆⽯⼦的重量*/private static int[] weight;/*** 记录⽯⼦堆断开的位置【便于计算局部最优解】*/private static int[][] location;/*** 记录⽯⼦堆局部最优解,以⾄于求得最终最优解【动规⽅程】*/private static int[][] dp;/*** 初始化数据*/private static void initData() {Scanner input = new Scanner(System.in);System.out.println("请输⼊⽯⼦堆数量:");num = input.nextInt();weight = new int[num];System.out.println("请输⼊每堆⽯⼦的重量:");for (int i = 0; i < weight.length; i++) {weight[i] = input.nextInt();}// 定义成 int 类型的⼆维数组,创建完每个元素直接初始化为 0dp = new int[num][num];location = new int[num][num];}/*** 计算最省最费⼒⽓值*/private static void dpFindMinStrength() {// 初始化 dp 数组for (int m = 0; m < num; m++) {dp[m][m] = 0; // ⼀堆⽯⼦,不⽤搬,耗费⼒⽓为 0}for (int r = 2; r <= num; r++) { // 从 2 堆依次到 num 堆,分别计算最优值for (int i = 0; i < num - r + 1; i++) { // 起始⽯⼦堆取值范围int j = i + r - 1; // 根据每次选取⽯⼦堆 r 和起始⽯⼦堆 i ,计算终⽌⽯⼦堆int sum = 0;for (int x = i; x <= j; x++) { // 计算从⽯⼦堆 i 到⽯⼦堆 j 合并时,最后两堆使⽤的⼒⽓总和 sumsum += weight[x];}// 根据动规⽅程,从局部最优解中计算当前从⽯⼦堆 i 到⽯⼦堆 j 合并所使⽤的的⼒⽓总和dp[i][j] = dp[i + 1][j] + sum; // 计算从 i ⽯⼦堆分开时,使⽤的⼒⽓总和location[i][j] = i; // 标记从第 i ⽯⼦堆分开位置for (int k = i + 1; k < j; k++) { // 需要统计从 k 【k ∈ (i, j)】⽯⼦堆分开,使⽤的⼒⽓总和int temp = dp[i][k] + dp[k + 1][j] + sum; // 计算从 k ⽯⼦堆分开时,使⽤的⼒⽓总和if (temp < dp[i][j]) {dp[i][j] = temp;location[i][j] = k;}}}}}/*** 输出*/private static void print() {System.out.println("动规数组【不同堆数合并⽯⼦所费⼒⽓】:");for (int i = 0; i < num; i++) {for (int j = 0; j < num; j++) {System.out.print(dp[i][j] + " ");}System.out.println();}System.out.println("不同堆数合并⽯⼦最省⼒⽓断开位置最优解:");for (int i = 0; i < num; i++) {for (int j = 0; j < num; j++) {System.out.print(location[i][j] + " ");}System.out.println();}}public static void main(String[] args) {// 初始化数据initData();// 计算最省最费⼒⽓值dpFindMinStrength();// 输出print();}}⽯⼦合并核⼼代码(5)输⼊输出请输⼊⽯⼦堆数量:4请输⼊每堆⽯⼦的重量:4 45 9动规数组【不同堆数合并⽯⼦所费⼒⽓】:0 8 21 430 0 9 270 0 0 140 0 0 0不同堆数合并⽯⼦最省⼒⽓分开位置最优解【下标从数组 0 开始分开】:0 0 1 20 0 1 20 0 0 20 0 0 0输⼊输出(6)总结 ⽯⼦合并问题完全提现了动态规划的核⼼思想,先求解⼦问题的解【⼦问题求解不相互独⽴,相互依赖】,然后从这些⼦问题的解中得到原问题的解,进⽽得到该问题的最优解。

石子合并问题

石子合并问题

石子合并问题
石子合并问题是最经典的DP问题。

首先它有如下3种题型:
(1)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。

求将这N堆石子合并成一堆的总花费最小(或最大)。

分析:当然这种情况是最简单的情况,合并的是任意两堆,直接贪心即可,每次选择最小的两堆合并。

本问题实际上就是哈夫曼的变形。

(2)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。

求将这N堆石子合并成一堆的总花费最小(或最大)。

分析:我们熟悉矩阵连乘,知道矩阵连乘也是每次合并相邻的两个矩阵,那么石子合并可以用矩阵连乘的方式来解决。

设dp[i][j]表示第i到第j堆石子合并的最优值,sum[i][j]表示第i到第j 堆石子的总数量。

那么就有状态转移公式:
代码如下:(直线)
return 0;
}
(3)问题(2)的是在石子排列是直线情况下的解法,如果把石子改为环形排列,又怎么做呢?
分析:状态转移方程为:
其中有:
代码如下:(环形)
#include <stdio.h>
#include <string.h>
#define INF 10000
#define N 205
int mins[N][N];
int maxs[N][N];
int sum[N],a[N];
int minval,maxval;
int n;
int min(int a,int b)
{
return a<b?a:b;。

动态规划思想:石子合并问题

动态规划思想:石子合并问题

动态规划思想:⽯⼦合并问题描述:在⼀个圆形操场的四周摆放着n 堆⽯⼦。

现要将⽯⼦有次序地合并成⼀堆。

规定每次只能选相邻的2 堆⽯⼦合并成新的⼀堆,并将新的⼀堆⽯⼦数记为该次合并的得分。

试设计⼀个算法,计算出将n堆⽯⼦合并成⼀堆的最⼩得分和最⼤得分。

开始以为通过贪⼼算法可能很快解决问题,可是是⾏不通的。

⾸先我们可以把这么堆⽯⼦看成⼀列我们假如5堆的⽯⼦,其中⽯⼦数分别为7,6,5,7,100•按照贪⼼法,合并的过程如下:每次合并得分第⼀次合并 7 6 5 7 100 =11 第⼆次合并 7 11 7 100=18 第三次合并 18 7 100 =25第四次合并 25 100 =125总得分=11+18+25+125=179•另⼀种合并⽅案每次合并得分 第⼀次合并 7 6 5 7 100 ->13第⼆次合并 13 5 7 100->12第三次合并 13 12 100 ->25第四次合并 25 100 ->125总得分=13+12+25+125=175显然利⽤贪⼼来做是错误的,贪⼼算法在⼦过程中得出的解只是局部最优,⽽不能保证使得全局的值最优。

如果N-1次合并的全局最优解包含了每⼀次合并的⼦问题的最优解,那么经这样的N-1次合并后的得分总和必然是最优的。

因此我们需要通过动态规划算法来求出最优解。

在此我们假设有n堆⽯⼦,⼀字排开,合并相邻两堆的⽯⼦,每合并两堆⽯⼦得到⼀个分数,最终合并后总分数最少的。

我们设m(i,j)定义为第i堆⽯⼦到第j堆⽯⼦合并后的最少总分数。

a(i)为第i堆⽯⼦得⽯⼦数量。

当合并的⽯⼦堆为1堆时,很明显m(i,i)的分数为0; 当合并的⽯⼦堆为2堆时,m(i,i+1)的分数为a(i)+a(i+1); 当合并的⽯⼦堆为3堆时,m(i,i+2)的分数为MIN((m(i,i)+m(i+1,i+2)+sum(i,i+2)),(m(i,i+1)+m(i+2,i+2)+sum(i,i+2)); 当合并的⽯⼦堆为4堆时......代码实现如下:1 #include<stdio.h>2#define N 1003/*4 *求合并过程中5 *最少合并堆数⽬6 **/7int MatrixChain_min(int p[N],int n)8 {9//定义⼆维数组m[i][j]来记录i到j的合并过成中最少⽯⼦数⽬10 //此处赋值为-11112int m[N][N];13for(int x=1;x<=n;x++)14for(int z=1;z<=n;z++)15 {16 m[x][z]=-1;17 }1819int min=0;2021//当⼀个单独合并时,m[i][i]设为0,表⽰没有⽯⼦22for(int g = 1;g<=n;g++) m[g][g]=0;2324//当相邻的两堆⽯⼦合并时,此时的m很容易可以看出是两者之和25for(int i=1;i<=n-1;i++)26 {27int j=i+1;28 m[i][j]=p[i]+p[j];29 }3031//当相邻的3堆以及到最后的n堆时,执⾏以下循环32for(int r=3; r<=n;r++)33for(int i=1;i<=n-r+1;i++)34 {35int j = i+r-1; //j总是距离i r-1的距离36int sum=0;37//当i到j堆⽯⼦合并时最后⾥⾯的⽯⼦数求和得sum38for(int b=i;b<=j;b++)39 sum+=p[b];4041// 此时m[i][j]为i~j堆⽯⼦间以m[i][i]+m[i+1][j]+sum结果,这是其中⼀种可能,不⼀定是最优42 //要与下⾯的情况相⽐较,唉,太详细了4344 m[i][j] = m[i+1][j]+sum;4546//除上⾯⼀种组合情况外的其他组合情况47for(int k=i+1;k<j;k++)48 {49int t=m[i][k]+m[k+1][j]+sum;50if(t<m[i][j])51 m[i][j] = t;5253 }54 }55//最终得到最优解56 min=m[1][n];57return min;585960 }6162/*63 *求合并过程中64 *最多合并堆数⽬65 **/6667int MatrixChain_max(int p[N],int n)68 {69int m[N][N];70for(int x=1;x<=n;x++)71for(int z=1;z<=n;z++)72 {73 m[x][z]=-1;74 }757677int max=0;78//⼀个独⾃组合时79for(int g = 1;g<=n;g++) m[g][g]=0;80//两个两两组合时81for(int i=1;i<=n-1;i++)82 {83int j=i+1;84 m[i][j]=p[i]+p[j];85 }8687for(int r=3; r<=n;r++)88for(int i=1;i<=n-r+1;i++)89 {90int j = i+r-1;91int sum=0;92for(int b=i;b<=j;b++)93 sum+=p[b];94 m[i][j] = m[i+1][j]+sum;9596for(int k=i+1;k<j;k++)97 {98int t=m[i][k]+m[k+1][j]+sum;99if(t>m[i][j])100 m[i][j] = t;101102 }103 }104105 max=m[1][n];106return max;107108109 }110int main()111 {112int stone[N];113int min=0;114int max=0;115int n;116 scanf("%d",&n);117for(int i=1;i<=n;i++)118 scanf("%d",&stone[i]);119120 min= MatrixChain_min(stone,n);121 max= MatrixChain_max(stone,n);122123//因为题⽬要求圆的原因,要把所有情况都要考虑到,总共有n种情况。

石子合并dp表达式

石子合并dp表达式

石子合并dp表达式
石子合并问题可以使用动态规划来解决。

对于有N堆石子的情况,设
dp[i][j]表示将i至j之间的石子合并成一堆的最小花费。

初始时,对于任意i,都有dp[i][i]=0,因为合并一堆石子不需要花费。

对于区间[i,j],枚举合并点k,则该区间合并的最小花费为:dp[i][k]+ dp[k+1][j]+sum[i][j],其中sum[i][j]表示区间[i,j]中石子数量的和。

最终答案即为dp[1][n]。

对于有N堆石子,每次只能移动相邻的2堆石子合并的情况,最终的结果
为dp[1][n],表示合并全部石子的最小代价。

以上内容仅供参考,建议查阅相关资料文献,或者咨询数学领域专业人士,以获取更全面准确的信息。

石子合并问题实验报告

石子合并问题实验报告

一、实验目的1. 了解石子合并问题的背景和意义;2. 掌握石子合并问题的解决方法;3. 提高实验操作能力和数据分析能力。

二、实验原理石子合并问题是一个经典的数学问题,主要研究如何将若干个石子合并成若干个尽可能大的石子堆。

该问题在现实生活中具有广泛的应用,如城市规划、资源分配等。

实验通过模拟石子合并过程,寻找最优的合并策略。

三、实验材料1. 石子若干;2. 纸和笔;3. 计算器。

四、实验步骤1. 准备实验材料,将石子随机分成若干堆;2. 记录每堆石子的数量;3. 按照一定的合并策略进行合并,如从数量最少的一堆开始合并;4. 记录每次合并后的石子堆数量和数量;5. 重复步骤3和4,直到所有石子合并成若干个石子堆;6. 分析实验结果,总结最优合并策略。

五、实验结果与分析1. 实验结果通过多次实验,发现以下几种合并策略:(1)从数量最少的一堆开始合并;(2)从数量最多的一堆开始合并;(3)从数量相差最小的一堆开始合并。

2. 实验分析(1)从数量最少的一堆开始合并:该策略在合并过程中可以逐渐减少石子堆的数量,但可能导致石子堆的体积差异较大。

(2)从数量最多的一堆开始合并:该策略在合并过程中可以保持石子堆的体积相对稳定,但可能导致石子堆的数量较多。

(3)从数量相差最小的一堆开始合并:该策略在合并过程中可以平衡石子堆的数量和体积,但需要花费更多的时间进行筛选。

综上所述,从数量相差最小的一堆开始合并是一种较为合理的合并策略。

六、实验结论1. 通过石子合并问题实验,了解了石子合并问题的背景和意义;2. 掌握了石子合并问题的解决方法,即从数量相差最小的一堆开始合并;3. 提高了实验操作能力和数据分析能力。

七、实验心得1. 在实验过程中,要注重观察和分析,以便找到最优的合并策略;2. 要善于总结经验,提高实验效率;3. 要注重团队合作,共同完成实验任务。

八、实验展望石子合并问题在现实生活中具有广泛的应用,未来可以从以下几个方面进行深入研究:1. 探讨不同合并策略的优缺点,寻找更优的合并策略;2. 将石子合并问题与其他实际问题相结合,如城市规划、资源分配等;3. 利用计算机技术,模拟石子合并过程,提高实验效率。

合并石子问题的算法讨论

合并石子问题的算法讨论

1 +2 + 3 + = 8 + 4 211 6 7 7 7 4
算法三、GARSIAWACHS算法
算法三、GARSIAWACHS算法
【引理1】
【引理2】
【引理2】Βιβλιοθήκη 【引理2】【引理2】
【引理3】
【引理4】
GARSIAWACHS算法实现
GARSIAWACHS算法实现
平衡树维护优化 对于这个算法,我们只需要维护一个2-递减
序列就可以了,算法的精髓在于每次寻找一个
最小的k。先把n个数从后往前扫一遍,将不满 足2-递减性质的数加进树中,每次更新序列时, 通过平衡树来维护,最后算法时间复杂度为 O(nlogn)。
总结

在证明GarsiaWachs算法的过程中,我们多次用 到反证法的思想,通过假设反例成立,推出矛盾来 证明,这是一种思想,运用这种思想,能够解决很
么我们就把a[k]与a[k-1]合并,之后向前找最大 的一个满足a[j-1]>a[k]+a[k-1]的j,把合并后的值 a[k]+a[k-1]插入a[j]的前面,最后的答案就是每 次a[k]与a[k-1]合并的代价和。
算法三、GARSIAWACHS算法
原数列: 20 6 10 11 25 12
20 371011 25 12 20616 37 12 12 27 47 25 25 20 11 27 20
算法三、 GARSIAWACHS算法
假设第i个石子的权值为a[i]。 对于节点k,如果a[k-1]>a[k+1],那
么,称节点k满足2-递减性质。
算法三、 GARSIAWACHS算法
GarsiaWachs算法大意:

石子合并问题原理

石子合并问题原理

石子合并问题原理
石子合并问题是一个经典的动态规划问题。

假设有n堆石子,每堆石子的数量分别为a_1, a_2, ..., a_n。

当合并两堆石子时,合并的代价为两堆石子的数量之和。

最终目标是通过一系列合并操作,使得最终只剩下一堆石子,并且合并的总代价最小。

为了解决这个问题,可以使用动态规划的思想。

定义一个二维数组dp,其中dp[i][j]表示从第i堆石子到第j堆石子合并的最小代价。

当i=j时,即只有一堆石子,不需要进行任何合并,所以
dp[i][j]=0。

当i<j时,假设k是i和j之间的一个分割点,那么dp[i][j]的值可以通过枚举所有可能的分割点k来求得。

具体计算dp[i][j]的方法如下:
- 首先,将dp[i][j]初始化为一个较大的值,比如无穷大。

- 然后,枚举分割点k,计算合并左侧和右侧的代价,即
dp[i][k]+dp[k+1][j]+sum(a_i to a_j)。

- 最后,选取所有分割点中代价最小的那个作为dp[i][j]的值。

最终,dp[1][n]就是合并所有石子的最小代价。

实际上,可以通过动态规划的方式先计算出所有长度为2、3、...、n的子问题的解,然后再利用这些子问题的解来递推计
算整个问题的解,这就是动态规划的思想。

总结起来,石子合并问题的原理就是使用动态规划,通过定义状态和状态转移方程,逐步递推计算出最终的结果。

石子合并问题(矩阵连乘求解)

石子合并问题(矩阵连乘求解)

石子合并问题 (2011-11-14 21:06:38)描述:在一条直线上摆着N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为将的一堆石子的数量。

设计一个算法,将这N堆石子合并成一堆的总花费最小解析:采用动态递归,运用矩阵连乘思想即可解答此类题目源程序:#include <cstdlib>#include <cstdio>#include <cmath>#include <algorithm>using namespace std;#define MAXN 100int sum[MAXN][MAXN];int m[MAXN][MAXN];int t[MAXN][MAXN];int n, stone[MAXN];int MatrixChain_min( ){int min=0;for(int g =01;g<n;g++)m[g][g]=0;for(int i=0;i<n-1;i++){int j=i+1;m[i][j]=sum[i][j];}for(int r=3; r<=n;r++)for(int i=0;i<n-r+1;i++){int j = i+r-1;m[i][j] = m[i+1][j]+sum[i][j];t[i][j] = i;for(int k=i+1;k<j;k++){int l=m[i][k]+m[k+1][j]+sum[i][j];if(l < m[i][j]){ m[i][j] = l;t[i][j] = k;}}}min=m[0][n-1];return min;}int main(){ int i,j,q;scanf("%d", &n);for(i = 0; i < n; i++)scanf("%d", &stone[i]); for(i=0;i<n;i++){for(j=i+1;j<n;j++){int temp=0;for(q=i;q<=j;q++)temp=temp+stone[q];sum[i][j]=temp;}}int Best=MatrixChain_min(); printf("%d\n",Best);return 0;}。

动态规划-石子合并

动态规划-石子合并
动态规划-石子合并
目录
• 问题描述与背景 • 动态规划基础 • 石子合并问题动态规划解法 • 案例分析与实践 • 拓展应用与变体问题 • 总结与展望
问题描述与背景
01
石子合并问题介绍
问题描述
给定一个由正整数组成的环形数组,表示一堆环形排列的石子。每次操作可以选 择相邻的两堆石子进行合并,合并的代价为两堆石子的重量之和。求将所有石子 合并为一堆的最小代价。
算法性能评估与比较
时间复杂度分析
空间复杂度分析
动态规划算法的时间复杂度通常为 O(n^3),其中n为石子的堆数。这是 因为需要枚举所有可能的区间和断点, 并进行状态转移。对于较大的n,可能 需要优化算法以降低时间复杂度。
动态规划算法的空间复杂度通常为 O(n^2),其中n为石子的堆数。这是 因为需要使用一个二维数组来存储状 态转移的结果。可以通过滚动数组等 技巧优化空间复杂度。
与其他算法的比较
除了动态规划算法外,还可以使用贪 心算法、分治算法等来解决石子合并 问题。贪心算法通常具有较低的时间 复杂度,但可能无法得到最优解。分 治算法可以将问题分解为多个子问题 分别求解,但可能涉及大量的重复计 算。动态规划算法可以在保证得到最 优解的同时,避免重复计算,问题,且子问题的最优解 可以推出原问题的最优解。
无后效性
某个阶段的状态一旦确定, 则此后过程的发展不受此 前各段状态的影响。
石子合并问题动态规
03
划解法
状态定义与转移方程
状态定义
设 $f[i][ j]$ 表示将第 $i$ 堆到第 $j$ 堆石子合并成一堆的最 小/最大代价。
明确问题的边界条件,即最小子问题的解。
定义状态
为每个子问题定义一个状态,通常用一个数 组或哈希表来存储状态值。

动态规划 —— 区间 DP —— 石子合并三讲

动态规划 ——  区间 DP —— 石子合并三讲
2.分析:
我们熟悉矩阵连乘,知道矩阵连乘也是每次合并相邻的两个矩阵,那么石子合并可以用矩阵连乘的方式来解 决。
设 dp[i][j] 表示第 i 到第 j 堆石子合并的最优值,sum[i][j] 表示第 i 到第 j 堆石子的总数量。
那么就有状态转移方程:
3.实现
int dp[N][N]; int sum[N]; int a[N]; int getMinval(int a[],int n) {
dp[i][i] = 0; p[i][i] = i; } for(int len=1; len<n; len++) { for(int i=1; i+len<=n; i++) {
int end = i+len; int tmp = INF; int k = 0; for(int j=p[i][end-1]; j<=p[i+1][end]; j++) {
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
for(int i = 0; i < n; i++) { qsort(&a[i], n - i, sizeof(a[0]), Compare);
for(int j = 0; j < n; j++) printf("%d ", a[j]);
if(i+j >= n) return getsum(i,n-i-1) + getsum(0,(i+j)%n);
else return sum[i+j] - (i>0 ? sum[i-1]:0);

noip2006提高组解题报告及源程序

noip2006提高组解题报告及源程序

NOIP2006 Solutions2006届中国信息学奥林匹克联赛提高组解题报告by FaLLeNs问题一能量项链/Energy核心算法动态规划(原型石子合并问题)时间复杂度O(n3)空间复杂度O(n2)编码复杂度<50行实测时间<0.1s分析记head[i]为第i颗能量珠的首标记,也同时是它前面那颗能量珠的尾标记。

记录Energy[i,j]为从以Head[i]为首标记的能量珠开始顺时针数到以Head[j]为尾标记的能量珠为止所有能量珠组成的串合并后放出的最大能量。

那么对于Energy[i,j],若先合并从以Head[i]为首标记的能量珠开始顺时针数到以Head[k]为尾标记的能量珠为止的串,在合并以Head[k]为首标记的能量珠开始顺时针数到以Head[j]为尾标记的能量珠为止的串,然后将这两颗合并后的能量珠合并,放出的能量为Energy[i,k]+Energy[k,j]+Head[i]*Head[k]*Head[j]。

也就是说,状态转移方程为Energy[i,j]=Max{Energy[i,k]+Energy[k,j]+Head[i]*Head[k]*Head[j]} (i<=k<=j (i<j), k<i or k>j (i>=j))。

计算Energy[i,j]时,只会用到含有能量珠数目比它少的串在数组中的值,所以可以以串的能量珠数目为顺序规划,即按照k|(k+i) mod n=j的顺序进行规划。

评论如果枚举将能量项链变为串的开链位置,需要约n/2倍计算量,得到一个O(n4)的时间复杂度。

这是不需要的。

一个较优的贪心近似算法是每次找首标记最小的能量珠与它前面的能量珠合并。

对于NOIP中30%数据是正确的。

一个典型的反例是n=4,Head={10,3,2,3},贪心算法得到3*2*3+10*3*3+10*10*3=408,而最优解为10*3*2+10*2*3+10*10*3=420。

[动态规划]石子合并问题

[动态规划]石子合并问题

[动态规划]⽯⼦合并问题https:///onlinejudge2/index.php/Home/Contest/contestproblem/cid/3016/pid/1729⽯⼦合并问题Time Limit: 1000 ms Memory Limit: 65536 KiBProblem Description在⼀个圆形操场的四周摆放着n堆⽯⼦。

现要将⽯⼦有次序地合并成⼀堆。

规定每次只能选相邻的2 堆⽯⼦合并成新的⼀堆,并将新的⼀堆⽯⼦数记为该次合并的得分。

试设计⼀个算法,计算出将n堆⽯⼦合并成⼀堆的最⼩得分和最⼤得分。

对于给定n堆⽯⼦,计算合并成⼀堆的最⼩得分和最⼤得分。

Input输⼊数据的第1⾏是正整数n,1≤n≤100,表⽰有n堆⽯⼦。

第⼆⾏有n个数,分别表⽰每堆⽯⼦的个数。

Output输出数据有两⾏,第1⾏中的数是最⼩得分,第2⾏中的数是最⼤得分。

Sample Input44 45 9Sample Output4354算法考试考完了(题⽬有点简单侥幸AK..),但这个问题当时困扰我了很久,⽹上解答的很模糊,因此还是在这⾥记录⼀下吧。

这题的难点是:如果将环形转换为直线(直线的⽹上有很多解答,这⾥不做赘述)。

其核⼼思想就是:通过将数量变为 2n-1 来转换成直线问题。

当时看到这⼏⾏字困惑了很久,⼀直不太理解,但之后画图就懂了。

1. 我们为了将环形变为直线,必须规定转动顺序,这⾥采⽤逆时针转动,且以 i 作为起点,j作为终点。

(右下图)2. 当规定好终点了,那么这环形有4种情况,我们求在这四种情况下最下的。

(右上图)3. 关于转换成直线,⽐如存在 a(0) -> b(1) -> c(2) -> d(3) 与 d(3) -> a(4) -> b(5) -> c(6)。

第⼀条是 i=0,j=3的数组,第⼆条是 i=3,j=6 的数组。

这样,我们就不⽤返回去计算了( 3->0->1->2 )。

石子合并(动态规划)详细解题报告

石子合并(动态规划)详细解题报告

二.算法分析竞赛中多数选手都不约而同地采用了尽可能逼近目标的贪心法来逐次合并:从最上面的一堆开始,沿顺时针方向排成一个序列。

第一次选得分最小(最大)的相邻两堆合并,形成新的一堆;接下来,在N-1堆中选得分最小(最大)的相邻两堆合并……,依次类推,直至所有石子经N-1次合并后形成一堆。

例如有6堆石子,每堆石子数(从最上面一堆数起,顺时针数)依次为346542要求选择一种合并石子的方案,使得做5次合并,得分的总和最小。

按照贪心法,合并的过程如下:每次合并得分第一次合并346542 ->5第二次合并54654 ->9第三次合并9654 ->9第四次合并969 ->15第五次合并159 ->2424总得分=5+9+9+15+24=62但是当我们仔细琢磨后,可得出另一个合并石子的方案:每次合并得分第一次合并346542 ->7第二次合并76542 ->13第三次合并13542 ->6第四次合并1356 ->11第五次合并1311 ->2424总得分=7+6+11+13+24=61显然,后者比贪心法得出的合并方案更优。

题目中的示例故意造成一个贪心法解题的假像,诱使读者进入“陷阱”。

为了帮助读者从这个“陷阱”里走出来,我们先来明确一个问题:1.最佳合并过程符合最佳原理使用贪心法至所以可能出错,是因为每一次选择得分最小(最大)的相邻两堆合并,不一定保证余下的合并过程能导致最优解。

聪明的读者马上会想到一种理想的假设:如果N-1次合并的全局最优解包含了每一次合并的子问题的最优解,那么经这样的N-1次合并后的得分总和必然是最优的。

例如上例中第五次合并石子数分别为13和11的相邻两堆。

这两堆石头分别由最初的第1,2,3堆(石头数分别为3,4,6)和第4,5,6堆(石头数分别为5,4,2)经4次合并后形成的。

于是问题又归结为如何使得这两个子序列的N-2次合并的得分总和最优。

石子合并问题(动态规划)

石子合并问题(动态规划)

⽯⼦合并问题(动态规划)⼀条直线上摆放着⼀⾏共n堆的⽯⼦。

现要将⽯⼦有序地合并成⼀堆。

规定每次只能选相邻的两堆合并成新的⼀堆,并将新的⼀堆⽯⼦数记为该次合并的得分。

请编辑计算出将n堆⽯⼦合并成⼀堆的最⼩得分和将n堆⽯⼦合并成⼀堆的最⼤得分。

Input输⼊有多组测试数据。

每组第⼀⾏为n(n<=100),表⽰有n堆⽯⼦,。

⼆⾏为n个⽤空格隔开的整数,依次表⽰这n堆⽯⼦的⽯⼦数量ai(0<ai<=100)Output每组测试数据输出有⼀⾏。

输出将n堆⽯⼦合并成⼀堆的最⼩得分和将n堆⽯⼦合并成⼀堆的最⼤得分。

中间⽤空格分开。

间⽤空格分开。

Sample Input31 2 3Sample Output9 111 #include<iostream>2 #include <cstdio>3 #include<cstring>4 #include<algorithm>5using namespace std;6#define INF 0x3f3f3f3f78int dp[105][105];9int sum[110];10int ans=0,cnt=0;1112int dfs(int l,int r){/*求最⼤*/13if(dp[l][r]!=-1){14return dp[l][r];15 }16if(r-l==1)return sum[r]-sum[l-1];17if(r==l) return0;18int &res=dp[l][r];19 res=-INF;20for(int k=l;k<r;k++){21 res=max(res,dfs(l,k)+dfs(k+1,r));22 }23 res+=sum[r]-sum[l-1];24return res;25 }2627int dfs2(int l,int r){/*求最⼩*/28if(dp[l][r]!=-1) return dp[l][r];29if(r-l==1) return sum[r]-sum[l-1];30if(r==l) return0;31 dp[l][r]=INF;32for( int k=l; k<r; k++ ){33 dp[l][r]=min(dp[l][r],dfs2(l,k)+dfs2(k+1,r));34 }35 dp[l][r]+=sum[r]-sum[l-1];36return dp[l][r];37 }3839int main(){40int n;41 memset(sum,0,sizeof(sum));42while(~scanf("%d",&n)&&n){43for( int i=1; i<=n; i++ ){44int t;45 cin>>t;46 sum[i]=sum[i-1]+t;47 }48 memset(dp,-1,sizeof(dp));49 ans=dfs2(1,n);50 memset(dp,-1,sizeof(dp));51 cnt=dfs(1,n);52 cout<<ans<<""<<cnt<<endl;53 }5455return0;56 }。

经典的石子合并

经典的石子合并

经典的石子合并问题描述:在一个圆形操场的四周摆放着n堆石子。

现要将石子有次序地合并成一堆。

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

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

对于给定n 堆石子,编程计算合并成一堆的最小得分和最大得分,并输出具体合并过程。

我觉得这是一道很有难度的动态规划算法题,如果不看资料,绝对是无法正确得到状态转移方程与输出最优解的合并过程的。

下面我整理了一下该问题的具体算法分析设计过程。

DP算法分析与设计:我们把每一次合并划分为阶段,当前阶段中计算出的得分和作为前一次合并的基础上定义一个能使目前得分总和最大的合并方案作为一次决策。

显然,某阶段的状态给阶段的决策不受这阶段以前各段状态的影响。

这种无后效性的性质符最佳原理,因此可采用动态规划算法。

DP状态表示:我们用〔i,j〕表示一个从第i堆数起,顺时针数j堆时的子序列第i堆、第i+1堆、……(i+j-1) mod n堆}。

为了输出最优解的方便,我们定义一个结构体Node,它包含两个域c --- 得分;d --- 最优化选择时子序列1的堆数。

有:f[i][j]表示一个从第i堆数起,顺时针数j堆时的子序列(i, i+1... (i+j-1)%n+1)(1) 在该子序列的各堆石子合并成一堆的过程中,各次合并得分的总和---c(2) 形成最佳得分和的子序列1和子序列2。

由于两个子序列是相邻的,因此只需记住子序列1的堆数k即可--- dDP边条及状态转移方程:(1) 边条:显然,对每一堆石子来说,仅含一堆石子的序列不存在合并有:f[i][1].c = 0,f[i][1].d = 0 (1 <= i <= N)(2) 方程:对子序列(i,j)最后一次合并,其得分为第i堆数起,顺时针数j堆的石子总数t。

被合并的两堆石子是由子序列(i, k)和((i+k-1)%n+1, j-k)(1<=k<=j-1)经有限次合并形成的。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

二.算法分析竞赛中多数选手都不约而同地采用了尽可能逼近目标的贪心法来逐次合并:从最上面的一堆开始,沿顺时针方向排成一个序列。

第一次选得分最小(最大)的相邻两堆合并,形成新的一堆;接下来,在N-1堆中选得分最小(最大)的相邻两堆合并……,依次类推,直至所有石子经N-1次合并后形成一堆。

例如有6堆石子,每堆石子数(从最上面一堆数起,顺时针数)依次为346542要求选择一种合并石子的方案,使得做5次合并,得分的总和最小。

按照贪心法,合并的过程如下:每次合并得分第一次合并346542 ->5第二次合并54654 ->9第三次合并9654 ->9第四次合并969 ->15第五次合并159 ->2424总得分=5+9+9+15+24=62但是当我们仔细琢磨后,可得出另一个合并石子的方案:每次合并得分第一次合并346542 ->7第二次合并76542 ->13第三次合并13542 ->6第四次合并1356 ->11第五次合并1311 ->2424总得分=7+6+11+13+24=61显然,后者比贪心法得出的合并方案更优。

题目中的示例故意造成一个贪心法解题的假像,诱使读者进入“陷阱”。

为了帮助读者从这个“陷阱”里走出来,我们先来明确一个问题:1.最佳合并过程符合最佳原理使用贪心法至所以可能出错,是因为每一次选择得分最小(最大)的相邻两堆合并,不一定保证余下的合并过程能导致最优解。

聪明的读者马上会想到一种理想的假设:如果N-1次合并的全局最优解包含了每一次合并的子问题的最优解,那么经这样的N-1次合并后的得分总和必然是最优的。

例如上例中第五次合并石子数分别为13和11的相邻两堆。

这两堆石头分别由最初的第1,2,3堆(石头数分别为3,4,6)和第4,5,6堆(石头数分别为5,4,2)经4次合并后形成的。

于是问题又归结为如何使得这两个子序列的N-2次合并的得分总和最优。

为了实现这一目标,我们将第1个序列又一分为二:第1、2堆构成子序列1,第3堆为子序列2。

第一次合并子序列1中的两堆,得分7;第二次再将之与子序列2的一堆合并,得分13。

显然对于第1个子序列来说,这样的合并方案是最优的。

同样,我们将第2个子序列也一分为二;第4堆为子序列1,第5,6堆构成子序列2。

第三次合并子序列2中的2堆,得分6;第四次再将之与子序列1中的一堆合并,得分13。

显然对于第二个子序列来说,这样的合并方案也是最优的。

由此得出一个结论──6堆石子经过这样的5次合并后,得分的总和最小。

我们把每一次合并划分为阶段,当前阶段中计算出的得分和作为状态,如何在前一次合并的基础上定义一个能使目前得分总和最大的合并方案作为一次决策。

很显然,某阶段的状态给定后,则以后各阶段的决策不受这阶段以前各段状态的影响。

这种无后效性的性质符最佳原理,因此可以用动态规划的算法求解。

2.动态规划的方向和初值的设定采用动态规划求解的关键是确定所有石子堆子序列的最佳合并方案。

这些石子堆子序列包括:{第1堆、第2堆}、{第2堆、第3堆}、……、{第N堆、第1堆};{第1堆、第2堆、第3堆}、{第2堆、第3堆、第4堆}、……、{第N堆、第1堆、第2堆};……{第1堆、……、第N堆}{第1堆、……、第N堆、第1堆}……{第N堆、第1堆、……、第N-1堆}为了便于运算,我们用〔i,j〕表示一个从第i堆数起,顺时针数j堆时的子序列{第i堆、第i+1堆、……、第(i+j-1)mod n堆}它的最佳合并方案包括两个信息:①在该子序列的各堆石子合并成一堆的过程中,各次合并得分的总和;②形成最佳得分和的子序列1和子序列2。

由于两个子序列是相邻的,因此只需记住子序列1的堆数;设f〔i,j〕──将子序列〔i,j〕中的j堆石子合并成一堆的最佳得分和;c〔i,j〕──将〔i,j〕一分为二,其中子序列1的堆数;(1≤i≤N,1≤j≤N)显然,对每一堆石子来说,它的f〔i,1〕=0c〔i,1〕=0(1≤i≤N)对于子序列〔i,j〕来说,若求最小得分总和,f〔i,j〕的初始值为∞;若求最大得分总和,f〔i,j〕的初始值为0。

(1≤i≤N,2≤j≤N)。

动态规划的方向是顺推(即从上而下)。

先考虑含二堆石子的N个子序列(各子序列分别从第1堆、第2堆、……、第N堆数起,顺时针数2堆)的合并方案f〔1,2〕,f〔2,2〕,……,f〔N,2〕c〔1,2〕,c〔2,2〕,……,c〔N,2〕然后考虑含三堆石子的N个子序列(各子序列分别从第1堆、第2堆、……、第N堆数起,顺时针数3堆)的合并方案f〔1,3〕,f〔2,3〕,……,f〔N,3〕c〔1,3〕,c〔2,3〕,……,c〔N,3〕……依次类推,直至考虑了含N堆石子的N个子序列(各子序列分别从第1堆、第2堆、……、第N堆数起,顺时针数N堆)的合并方案f〔1,N〕,f〔2,N〕,……,f〔N,N〕c〔1,N〕,c〔2,N〕,……,c〔N,N〕最后,在子序列〔1,N〕,〔2,N〕,……,〔N,N〕中,选择得分总和(f值)最小(或最大)的一个子序列〔i,N〕(1≤i≤N),由此出发倒推合并过程。

3.动态规划方程和倒推合并过程对子序列〔i,j〕最后一次合并,其得分为第i堆数起,顺时针数j堆的石子总数t。

被合并的两堆石子是由子序列〔i,k〕和〔(i+k-1)modn+1,j-k〕(1≤k≤j-1)经有限次合并形成的。

为了求出最佳合并方案中的k 值,我们定义一个动态规划方程:当求最大得分总和时f〔i,j〕=max{f〔i,k〕+f〔x,j-k〕+t}1≤k≤j-1c〔i,j〕=k│ f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t(2≤j≤n,1≤i≤n)当求最小得分总和时f〔i,j〕=min{f〔i,k〕+f〔x,j-k〕+t}1≤k≤j-1c〔i,j〕=k│ f〔i,j〕=f〔i,k〕+f〔x,j-k〕+t(2≤j≤n,1≤i≤n)其中x=(i+k-1)modn+1,即第i堆数起,顺时针数k+1堆的堆序号。

例如对上面例子中的6(346542 )堆石子,按动态规划方程顺推最小得分和。

依次得出含二堆石子的6个子序列的合并方案f〔1,2〕=7 f〔2,2〕=10 f〔3,2〕=11c〔1,2〕=1 c〔2,2〕=1 c〔3,2〕=1f〔4,2〕=9 f〔5,2〕=6 f〔6,2〕=5c〔4,2〕=1 c〔5,2〕=1 c〔6,2〕=1含三堆石子的6(346542 )个子序列的合并方案f〔1,3〕=20 f〔2,3〕=25 f〔3,3〕=24c〔1,3〕=2 c〔2,3〕=2 c〔3,3〕=1f〔4,3〕=17 f〔5,3〕=14 f〔6,3〕=14c〔4,3〕=1 c〔5,3〕=1 c〔6,3〕=2含四堆石子的6(346542 )个子序列的合并方案f〔1,4〕=36 f〔2,4〕=38 f〔3,4〕=34c〔1,4〕=2 c〔2,4〕=2 c〔3,4〕=1f〔4,4〕=28 f〔5,4〕=26 f〔6,4〕=29c〔4,4〕=1 c〔5,4〕=2 c〔6,4〕=3含五堆石子的6(346542 )个子序列的合并方案f〔1,5〕=51 f〔2,5〕=48 f〔3,5〕=45c〔1,5〕=3 c〔2,5〕=2 c〔3,5〕=2f〔4,5〕=41 f〔5,5〕=43 f〔6,5〕=45c〔4,5〕=2 c〔5,5〕=3 c〔6,5〕=3含六堆石子的6(346542 )个子序列的合并方案f〔1,6〕=61 f〔2,6〕=62 f〔3,6〕=61c〔1,6〕=3 c〔2,6〕=2 c〔3,6〕=2f〔4,6〕=61 f〔5,6〕=61 f〔6,6〕=62c〔4,6〕=3 c〔5,6〕=4 c〔6,6〕=3f〔1,6〕是f〔1,6〕,f〔2,6〕,……f〔6,6〕中的最小值,表明最小得分和是由序列〔1,6〕经5次合并得出的。

我们从这个序列出发,按下述方法倒推合并过程:由c〔1,6〕=3可知,第5次合并的两堆石子分别由子序列〔1,3〕和子序列〔4,3〕经4次合并后得出。

其中c〔1,3〕=2可知由子序列〔1,3〕合并成的一堆石子是由子序列〔1,2〕和第三堆合并而来的。

而c〔1,2〕=1,以表明了子序列〔1,2〕的合并方案是第1堆合并第2堆。

由此倒推回去,得出第1,第2次合并的方案,每次合并得分第一次合并346……->7第二次合并76…… ->1313……子序列〔1,3〕经2次合并后合并成1堆,2次合并的得分和=7+13=20。

c〔4,3〕=1,可知由子序列〔4,3〕合并成的一堆石子是由第4堆和子序列〔5,2〕合并而来的。

而c〔5,2〕=1,又表明了子序列〔5,2〕的合并方案是第5堆合并第6堆。

由此倒推回去,得出第3、第4次合并的方案每次合并得分:第三次合并……542 ->6第四次合并……56 ->11……11子序列〔4,3〕经2次合并后合并成1堆,2次合并的得分和=6+11=17。

第五次合并是将最后两堆合并成1堆,该次合并的得分为24。

显然,上述5次合并的得分总和为最小20+17+24=61上述倒推过程,可由一个print(〔子序列〕)的递归算法描述procedure print (〔i,j〕)beginif j〈〉1 then {继续倒推合并过程beginprint(〔i,c〔i,j〕〕;{倒推子序列1的合并过程}print(〔i+c〔i,j〕-1〕mod n+1,j-c〔i,j〕){倒推子序列2的合并过程}for K:=1 to N do{输出当前被合并的两堆石子}if (第K堆石子未从圈内去除)then beginif(K=i)or(K=X)then置第K堆石子待合并标志else第K堆石子未被合并;end;{then}第i堆石子数←第i堆石子数+第X堆石子数;将第X堆石子从圈内去除;end;{then}end;{print}例如,调用print(〔1,6〕)后的结果如下:print(〔1,6〕)⑤┌──────┴──────┐print(〔1,3〕)② print(〔4,3〕)④┌─────┴─────┐┌─────┴─────┐print(〔1,2〕)① print(〔3,1〕) print(〔4,1〕) print(〔5,2〕)③┌──────┴──────┐┌──────┴──────┐print(〔1,1〕) print(〔2,1〕) print(〔5,1〕)print(〔6,1〕)(图6.2-5)其中回溯至①显示34654②显示76542③显示13542④显示1356⑤显示1311注:调用print过程后,应显示6堆石子的总数作为第5次合并的得分。

相关文档
最新文档