在一个圆形操场的四周摆放着n堆石子现要将石子有次序地
贪心
贪心法在实际生活中,经常需要求一个问题的可行解和最优解,这就是所谓的最优化问题。
每个最优化问题都包含一组限制条件(约束条件)和一个目标函数(优化函数),符合限制条件的问题求解方案称为可行解,使目标函数取得最佳值(最大或最小)的可行解称为最优解。
求解这类问题最原始的方法是搜索(穷举)法,一般就是回溯法。
当然,为了尽快搜索出解,我们往往会利用限制条件进行可行性判断剪枝,或利用目标函数的上界(或下界)进行分枝定界。
第二种解决此类问题的方法是动态规划,当然,使用动态规划必须要满足一些条件,如无后效性等。
贪心法是求解这类问题的另一种常用算法,它是从问题的某一个初始解出发,采用逐步构造最优解的方法向给定的目标前进。
在每个局部阶段,都做出一个看上去最优的决策(即某种意义下的、或某个标准下的局部最优解),并期望通过每次所做的局部最优选择产生出一个全局最优解。
所以,有些问题用贪心法求解能得到最优解,并且能够证明,而有些却不能证明(只能说暂时找不出反例),甚至并不能保证得到最优解,这时我们可以选择其它解决最优化问题的算法,如动态规划等。
做出贪心决策的依据称为贪心准则(贪心策略),注意贪心策略一旦做出,就不可再更改。
贪心与递推不同的是,严格讲,贪心只是一种策略或方法,而不是算法,推进的每一步不是依据某一固定的递推式,而是做一个当时看似最佳的贪心选择(操作),不断地将问题实例归纳为更小的相似子问题。
所以,归纳、分析、选择正确合适的贪心策略,是正确解决贪心问题的关键。
贪心法的优势在于编程简单、运行效率高、空间复杂度低等特点。
是信息学竞赛中的一个有为武器,受到广大同学们的青睐。
下面我们先看两个简单而典型的例子。
例1、删数问题[问题描述]键盘输入一个高精度的正整数n(≤240位),去掉其中任意s个数字后剩下的数字按原左右次序将组成一个新的正整数。
编程对给定的n和s,寻找一种方案,使得剩下的数字组成的新数最小。
[输入格式] ns[输出格式] 最后剩下的最小数。
作业二解答
解答二2007.5.12143-10. 有数字三角形形状如下L L 333231222111a a a a a a a n 1a n 2…a nn设计一个算法,计算出从三角形的顶到底的一条路径,使得该路径经过的数字总和达到最大。
63-12.给定n 个物品和一个背包,物品i 的重量是w i , 体积是b i , 价值是v i ,背包的容量为W , 容积为V 。
问如何选择装入背包的物品,使得物品总重量不超过背包的容量,总体积不超过背包的容积,同时总价值达到最大?设m (i,k,j )表示只考虑前i 种物品,体积限制为k ,重量限制为j 的最大价值,则}),,1(),,,1(max{),,(i i i v w j b k i m j k i m j k i m +−−−−=m (1,k ,j )=v 1k ≥b 1, j ≥w 1, m (1,k ,j )=0 k <b 1或j <w 1m (i ,k ,j )=0 若k <0 或j <0时间复杂度为O (nCD ).7补充题:有n 个底面为长方形的物品需要租用库房存放。
如果每个物品都必须放在地面上,且所有物品的底面宽度都等于库房的宽度,那么第i 个物品占用库房面积大小只需要用它的底面长度l i 来表示,i =1, 2, …, n 。
设库房总长度是L ,且,设库房单位长度的租金是一个常数,如果要求库房出租的收益达到最大,设计求解的算法。
L l n i i >∑=1解:设F i (l )为i 种物品,库房长度为l 时的最大有效长度F i (l )=max{F i -1(l -l i )+l i , F i -1(l )}F 1(l )=l 1, if l ≥l 1F 1(l )=0, if l <l 1F n (L )为解时间为O (nL )。
石子归并(动态规划)
石子归并(动态规划)动态规划石子合并问题【石材加固】在一个圆形操场的四周摆放着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。
动态规划的模型构建与优化方法
从上面的动态规划方程可以看出,该算法在 最坏的情况下时间复杂度将接近于 10000^2*100^3,由于最后要输出最优方案, 不可用滚动数组,因此它的空间复杂度为 o(10000*100^2),无论从时间还是空间上来 看,该算法都不适合于该问题。
算法二
只要仔细分析问题不难看出,该问题是要我们在输 入的第2至第N-1个数前面加减号,并且在这个式子 中添入N-1个括号,使得式子最终的计算结果为给定 的数T。我们不妨将所有的括号都拆掉,最后该式子 将会成为一个没有括号的加减式。注意:只要稍加 分析即可发现,该加减式的第二个数前面肯定是减 号。反过来考虑,如果一个加减式的第二个数前面 为减号,其余的数前面为“+”号或“-”号(第一 个前面没有符号),那么该式子能否变为一个由N-1 个括号和和减号组成的等价式呢?答案是肯定的。
重叠子结构
2 是否具有:
最优子结构
4
编程求解
3
构建对应模 型
动态规划是一种用于求解前后关联具有链状结构的多阶段决策过 程的最优化问题的方法
什么是动态规划?
(一)动态规划是解决多阶段决 策问题的一种方法。
多阶段决策问题
对于整个问题,可以根据其时间或 其他顺序分成若干个前后相关联的子问 题,问题的全局最优包含其子问题的局 部最优,即满足最优子结构性质,并且 无后效性,有边界条件,且一般划分为 很明显的阶段,存在一条或多条状态转 移方程。
记忆化搜索与动态规划
记忆化搜索实际上是一种递归形式的动态规划,所以它无论从 时间上还是从空间上来看都与后面的动态规划算法差不多。 记忆化搜索的特点是可以使动态规划算法看起来比较直观,它 能让我们比较清楚的看出动态规划是如何具体用空间换取时间 的。它的最大优点是,只要发现了搜索的重复计算之处,用它 可以很快的优化算法。 在竞赛中,我们可以先编一个搜索程序,如果这个题目可以用 动态规划解决,一般都可以采用记忆化搜索,这样我们可以在 事先有一个搜索保本的情况下考虑动态规划,最后即便是没有 想到怎样用记忆化搜索做,至少也会有一个搜索保本,比较划 算。
软件工程课程实习题目
软件工程课程实习题目1、设有n个运动员要进行网球循环赛。
设计一个满足以下要求的比赛日程表:(1)每个选手必须与其他n-1个选手各赛一次。
(2)每个选手一天只能赛一次。
(3)当n是偶数时,循环赛进行n-1天。
当n是奇数时,循环赛进行n天。
2、考虑国际象棋棋盘上某个位置的一只马,它是否可能只走63步,正好走过除起点外的其他63个位置各一次?如果有一种这样的走法,则称所走的这条路线为一条马的周游路线。
试设计一个程序输出这样的一条马的周游路线。
3、Gray码是一个长度为2n的序列。
序列中无相同元素,每个元素都是长度为n位的串,相邻元素恰好只有一位不同。
设计一个程序实现对任意的n构造相应的Gray码。
4、多边形游戏问题是1998年国际信息学奥林匹克竞赛试题。
该游戏是一个单人玩的游戏,开始时有一个由n个顶点构成的多边形。
每个顶点被赋予一个整数值,每条边被赋予一个运算符“+”或“*”。
所有边依次用整数从1到n编号。
游戏的地一步,将一条边删除。
随后n-1步按以下方式操作:(1)选择一条边E以及由E连接着的两个顶点V1和V2;(2)用一个新的顶点取代边E以及由E连接着的两个顶点V1和V2。
将由顶点V1和V2的整数值通过边E上的运算得到的结果赋予新的顶点;(3)最后,所有边都被删除,游戏结束。
游戏的得分就是所剩顶点上的整数值。
5、在一个圆形操场的四周摆放着n堆石子。
现要将石子有次序地合并成一堆。
规定每次只能选相邻的两堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
试设计一个程序,计算出将n堆石子合并成一堆的最小得分和最大得分。
6、商店中每种商品都有标价。
例如:一朵花的价格是2元,一个花瓶的价格是5元。
为了吸引顾客,商店提供了一组优惠商品。
优惠商品是把一种或多种商品分成一组,并降价销售。
例如:3朵花的价格不是6元而是5元。
2个花瓶加1朵花的优惠价格是10元。
试设计一个程序,计算出某个顾客所购商品应付的最少费用。
动态规划-石子合并问题
动态规划-⽯⼦合并问题(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)总结 ⽯⼦合并问题完全提现了动态规划的核⼼思想,先求解⼦问题的解【⼦问题求解不相互独⽴,相互依赖】,然后从这些⼦问题的解中得到原问题的解,进⽽得到该问题的最优解。
动态规划思想:石子合并问题
动态规划思想:⽯⼦合并问题描述:在⼀个圆形操场的四周摆放着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种情况。
动态规划习题精讲
信息学竞赛中的动态规划专题哈尔滨工业大学周谷越【关键字】动态规划动机状态典型题目辅助方法优化方法【摘要】本文针对信息学竞赛(面向中学生的Noi以及面向大学生的ACM/ICPC)中的动态规划算法,从动机入手,讨论了动态规划的基本思想和常见应用方法。
通过一些常见的经典题目来归纳动态规划的一般作法并从理论上加以分析和说明。
并介绍了一些解决动态规划问题时的一些辅助技巧和优化方法。
纵观全文可知,动态规划的关键在于把握本质思想的基础上灵活运用。
【目录】1.动态规划的动机和基本思想1.1.解决重复子问题1.2.解决复杂贪心问题2.动态规划状态的划分方法2.1.一维状态划分2.2.二维状态划分2.3.树型状态划分3.动态规划的辅助与优化方法3.1.常见辅助方法3.2.常见优化方法4.近年来Noi动态规划题目分析4.1 Noi2005瑰丽华尔兹4.2 Noi2005聪聪与可可4.3 Noi2006网络收费4.4 Noi2006千年虫附录参考书籍与相关材料1.动态规划的动机和基本思想首先声明,这里所说的动态规划的动机是从竞赛角度出发的动机。
1.1 解决重复子问题对于很多问题,我们利用分治的思想,可以把大问题分解成若干小问题,然后再把各个小问题的答案组合起来,得到大问题的解答。
这类问题的共同点是小问题和大问题的本质相同。
很多分治法可以解决的问题(如quick_sort,hanoi_tower等)都是把大问题化成2个以内的不相重复的小问题,解决的问题数量即为∑(log2n / k)。
而考虑下面这个问题:USACO 1.4.3 Number Triangleshttp://122.139.62.222/problem.php?id=1417【题目描述】考虑在下面被显示的数字金字塔。
写一个程序来计算从最高点开始在底部任意处结束的路径经过数字的和的最大。
每一步可以走到左下方的点也可以到达右下方的点。
73 88 1 02 7 4 44 5 2 6 1在上面的样例中,从7到3到8到7到5的路径产生了最大和:30。
习题1
习题11、在一个地图上有n个地窖(n≤20),每个地窖中埋有一定数量的地雷,同时,给出地窖之间的联系路径。
例如下图给出了一个示例,其中V1,V2,V3,...,V6表示地窖。
[题目要求]当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,然后可以沿着指出的连接往下挖(仅能选择一条路径),挖的过程中不允许某人重复经过地窖。
当无连接时,挖地雷工作结束。
设计一个挖地雷的方案,使某人能挖到最多的地雷。
输入:n (表示地窖的个数)W1 W2 W3......Wna12 (1)a23 (2).........a(n-1,n)表示地窖之间连接路径(其中aij表示地窖i,j之间是否有通路:通aij=1,不通aij=0)输出:R1-R2-...-Rk (挖地雷的顺序)max (为挖地雷的数量)2、设有1g,2g,3g,5g,10g,20g的砝码各若干枚(其总重≤1000g),要求:输入:a1 a2 a3 a4 a5 a6(表示1g砝码有a1个,2g砝码有a2个,......20g砝码有a6个) 输出:Total=n (n表示用这些砝码能称出的不同重量的个数,但不包括一个砝码也不用的情况)3、将整数n分成k份,且每种拆分方案不能为空,任意两种拆分方案不能相同(不考虑顺序)。
例如:n =7,k=3,下面三种分法被认为是相同的。
1, 1, 5; 1, 5, 1; 5, 1, 1;问有多少种不同的分法。
输入:n,k (6<n≤200, 2≤k≤6)输出:一个整数,即不同的分法。
4、有一个箱子容量为v(正整数,0≤v≤20000),同时有n个物品(0<n≤30),每个物品有一个体积(正整数)。
要求从n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
输入:箱子的容量v和物品数n。
接下来n行,分别表示这n个物品的体积输出:箱子剩余空间5、某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
动态规划(二)
上述算法每个状态转移的状态数为a[i], 每次状态转移的时间为O(1),状态总数 是所有值为true的状态的总数。
算法优化
本题在i较小时,值为true的状态也较少,但随着i的增大, 值为true的状态也急剧增多,影响了算法的时间效率。 我们分别求出从价值为1..3的大理石中选出部分大理石所能 获得的所有价值和,和从价值为4..6的大理石中选出部分大 理石所能获得的所有价值和。再判断是否存在和为Mid的价 值和,从而得出问题的解。
动态规划时间效率的优化
二、减少每个状态转移的状态数 在使用动态规划方法解题时,对当前状态 的计算都是进行一些决策并引用相应的已经计算过 的状态,这个过程称为“状态转移”。因此,每个状 态可能转移的状态数是决定动态规划算法时间复杂度 的一个重要因素。
动态规划时间效率的优化
1、决策量的优化 、 分析问题最优解的性质,缩小决策集合,也 可以减少每个状态可能转移的状态数。 NOI`96中的添加号问题,是从“所得的和最小” 这一原则出发,仅在等分点的附近添加号,从而 大大减少了每个状态转移的状态数,降低了算法 的时间复杂度。
动态规划模型的建立
1、一般动态规划 某些问题在状态的选择上会遇到一些困难, 但困难更多的集中表现在阶段的划分上,因 为阶段的特征并不明显。在这种情况下,通 常按状态最优值的大小划分阶段,并采用类 似搜索的方法解状态转移方程。
双层动态规划
农 田 个 数(count.pas) 你的老家在河北农村。过年时,你回老家去拜年。你家有一片N×M 农田,将其看成一个N×M的方格矩阵,有些方格是一片水域。你的农 村伯伯听说你是学计算机的,给你出了一道题: 他问你:这片农田总 共包含了多少个不存在水域的正方形农田。 两个正方形农田不同必须至少包含下面的两个条件中的一条: 边长不相等 左上角的方格不是同一方格
2020年算法分析设计习题答案
第3章 动态规划
2. 石子合并问题 问题描述: 在一个圆形操场的四周摆放着n堆石子. 现在要将石子有次序地合并 成一堆. 规定每次只能选相邻的2堆石子合并成一堆, 并将新的一堆石子数记为 该次合并的得分. 试设计一个算法, 计算出将n堆石子合并成一堆的最小得分和 最大得分. 算法设计: 对于给定n堆石子, 计算合并成一堆的最小得分和最大得分. 数据输入: 由文件input.txt提供输入数据. 文件的第1行是正整数n, 1n100, 表 示有n堆石子. 第2行有n个数, 分别表示n堆石子的个数. 结果输出: 将计算结果输出到文件output.txt, 文件第1行是最小得分, 第2行是最 大得分.
第五章 回溯
运动员最佳配对问题
问题描述: 羽毛球队有男女运动员各n人. 给定2个nn矩阵P和Q. P[i][j]是男运 动员i与女运动员j配混合双打的男运动员竞赛优势; Q[i][j]是女运动员i与男运 动员j配混合双打的女运动员竞赛优势. 由于技术配合和心理状态等各种因素 影响, P[i][j]不一定等于Q[j][i]. 男运动员i和女运动员j配对的竞赛优势是 P[i][j]*Q[j][i]. 设计一个算法, 计算男女运动员最佳配对法, 使得各组男女双方 竞赛优势的总和达到最大.
8.
若m[i,j]>t, 则m[i,j]=t; s[i,j]=k;
第3章 动态规划
再讨论圆周上的石子合并问题, 子结构[i:j]稍作修改 • 定义m[i][len]为合并第i堆到第i+len-1堆石子能得到的最少分数 • 当i+len-1>n时, 指跨过第n堆到第(i+len-1)%n堆,
仅sum函数需要修改
第2章 分治
2-8 设n个不同的整数排好序后存于T[1:n]中. 若存在一个下标i, 1 i n, 使得T[i]=i. 设计一个有效算法找到这个下标. 要求算 法在最坏情况下的计算时间O(log n).
经典的动态规划入门练习题
动态规划入门练习题1.石子合并在一个圆形操场的四周摆放着N堆石子(N<= 100),现要将石子有次序地合并成一堆.规定每次只能选取相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分.编一程序,由文件读入堆栈数N及每堆栈的石子数(<=20).(1)选择一种合并石子的方案,使用权得做N-1次合并,得分的总和最小;(2)选择一种合并石子的方案,使用权得做N-1次合并,得分的总和最大;输入数据:第一行为石子堆数N;第二行为每堆的石子数,每两个数之间用一个空格分隔.输出数据:从第一至第N行为得分最小的合并方案.第N+1行是空行.从第N+2行到第2N+1行是得分最大合并方案.每种合并方案用N行表示,其中第i行(1<=i<=N)表示第i次合并前各堆的石子数(依顺时针次序输出,哪一堆先输出均可).要求将待合并的两堆石子数以相应的负数表示.输入输出范例:输入:44 5 9 4输出:-459-4-8-59-13-9224-5-944-14-4-4-1822最小代价子母树设有一排数,共n个,例如:22 14 7 13 26 15 11.任意2个相邻的数可以进行归并,归并的代价为该两个数的和,经过不断的归并,最后归为一堆,而全部归并代价的和称为总代价,给出一种归并算法,使总代价为最小.输入、输出数据格式与“石子合并”相同。
输入样例:412 5 16 4输出样例:-12-516417-16-4-17-20372.背包问题设有n种物品,每种物品有一个重量及一个价值。
但每种物品的数量是无限的,同时有一个背包,最大载重量为XK,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于XK,而价值的和为最大。
输入数据:第一行两个数:物品总数N,背包载重量XK;两个数用空格分隔;第二行N个数,为N种物品重量;两个数用空格分隔;第三行N个数,为N种物品价值; 两个数用空格分隔;输出数据:第一行总价值;以下N行,每行两个数,分别为选取物品的编号及数量;输入样例:4 102 3 4 71 3 5 9输出样例:122 14 13.商店购物某商店中每种商品都有一个价格。
C++程序 石子合并问题
问题描述:在一个圆形操场的四周摆放着n 堆石子。
现要将石子有次序地合并成一堆。
规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。
【输入文件】包含两行,第1 行是正整数n(1<=n<=100),表示有n堆石子。
第2行有n个数,分别表示每堆石子的个数。
【输出文件】输出两行。
第1 行中的数是最小得分;第2 行中的数是最大得分。
【输入样例】44 45 9【输出样例】4354设计算法分析:1)、此题表面上看是用贪心算法比较合适,实则不然,用贪心算法是每次都合并得分最大的或最小的相邻两堆,但不一定保证余下的合并过程能导致最优解,聪明的读者马上会想到一种理想的假设:如果N-1次合并的全局最优解包含了每一次合并的子问题的最优解,那么经这样的N-1次合并后的得分总和必然是最优的,这就是动态规划的思想。
采用动态规划求解的关键是确定所有石子堆子序列的最佳合并方案。
这些石子堆子序列包括:{第1堆、第2堆}、{第2堆、第3堆}、……、{第N 堆、第1堆};{第1堆、第2堆、第3堆}、{第2堆、第3堆、第4堆}、……、{第N堆、第1堆、第2堆};……{第1堆、……、第N堆}{第2堆、……、第N堆、第1堆}……{第N堆、第1堆、……、第N-1堆}为了便于运算,我们用〔i,j〕表示一个从第i堆数起,顺时针数j堆时的子序列{第i堆、第i+1堆、……、第(i+j)堆}(注意:此时的i+j 可能已经超出N的范围,为此我们在读入每堆石子的数量时,把石子堆数扩展为2*N-1,有a[i]=a[i+N],没有a[N+N])它的最佳合并方案包括两个信息:②该子序列的各堆石子合并成一堆的过程中,各次合并得分的总和;②形成最佳得分和的子序列1和子序列2。
由于两个子序列是相邻的,因此只需记住子序列1的堆数;读者可以从以下详细的程序代码中体会到算法的思想:// StonesMerger.h#ifndef STONESMERGER_H#define STONESMERGER_Hclass StonesMerger{public:StonesMerger();~StonesMerger();void run(); // 运行接口private:int number; // 石子的堆数int totalMax; // 记录合并成一堆的最大得分int X; // 记录合并成一堆的最大得分的最优合并的起始堆int totalMin; // 记录合并成一堆的最小得分int Y; // 记录合并成一堆的最小得分的最优合并的起始堆int *a; // 存储石子的数量int **MAX; // 记录子序列合并最大得分int **Smax; // 记录子序列合并最大得分的最佳断开位置int **min; // 记录子序列合并最小得分int **smin; // 记录子序列合并最小得分的最佳断开位置bool input(); // 输入接口读取 input.txt 文件void mostValue(); // 以第 i 堆开始合并后面的 j 堆 j=0、1、2、......、n-1void output(); // 输出显示void traceback(int **s,int n,int m); // 对应于 Smax[n][m] 或smin[n][m] 寻找轨迹};#endif// StonesMerger.cpp#include <iostream.h>#include <fstream.h>#include <cstdlib>#include <iomanip.h>#include "StonesMerger.h"ifstream inputFile("input.txt",ios::in);ofstream outputFile("output.txt",ios::out);#define N 100 // 根据实际问题规模大小设定恰当的初值StonesMerger::StonesMerger(){number=0;X=0;Y=0;totalMax=0;totalMin=0;a=new int [2*N];MAX=new int *[2*N];Smax=new int *[2*N];min=new int *[2*N];smin=new int *[2*N];for (int i=0;i<2*N;i++){MAX[i]=new int [2*N];Smax[i]=new int [2*N];min[i]=new int [2*N];smin[i]=new int [2*N];}}StonesMerger::~StonesMerger() {delete []a;for (int i=0;i<2*N;i++){delete []MAX[i];delete []Smax[i];delete []min[i];delete []smin[i];}delete []MAX;delete []Smax;delete []min;delete []smin;}void StonesMerger::run() // 运行接口{if (input()){mostValue();output();}}bool StonesMerger::input(){if (!inputFile){cerr<<"input.txt could not be opened."<<endl;exit(1);}if (!outputFile){cerr<<"output.txt could not be opened."<<endl;exit(1);}inputFile>>number;outputFile<<"石子堆数为: "<<number<<endl;outputFile<<"各堆石子数量(堆编号为1-"<<number<<"):";for (int i=1;i<=number;i++){inputFile>>a[i];a[i+number]=a[i];outputFile<<setw(2)<<a[i]<<" ";}outputFile<<endl;if (a!=NULL){return true;}return false;}void StonesMerger::output(){outputFile<<"最小得分为: "<<totalMin<<endl;traceback(smin,Y,number);outputFile<<endl;outputFile<<"最大得分为: "<<totalMax<<endl;traceback(Smax,X,number);outputFile<<endl;}void StonesMerger::mostValue() // 寻找最优断开位置和最大 / 最小子序列{for (int i=1;i<=2*number-1;i++){MAX[i][1]=0; // 只有一堆不用合并得分为 0min[i][1]=0;}for (int r=2;r<=number;r++) // r 为合并的石子堆数,r==1,为当前一堆不合并得分为 0 ,r==2,合并后面的一堆{for (int i=1;i<=2*number-r;i++) // 以第i堆开始合并 r 堆{int t=0;int j=i;for (int m=1;m<=r;m++){t+=a[j];j++;}MAX[i][r]=MAX[i][1]+MAX[i+1][r-1]+t; // r 对合并最后一次的得分为原始 r 堆的总和Smax[i][r]=1;min[i][r]=min[i][1]+min[i+1][r-1]+t;smin[i][r]=1;for (int k=2;k<r;k++){int T1=MAX[i][k]+MAX[i+k][r-k]+t;int T2=min[i][k]+min[i+k][r-k]+t;if (T2<min[i][r]){min[i][r]=T2; // 确保子序列最优记录最优断开位置smin[i][r]=k;}if (T1>MAX[i][r]){MAX[i][r]=T1; // 确保子序列最优记录最优断开位置Smax[i][r]=k;}}}}totalMax=0;for (i=1;i<=number;i++){if (MAX[i][number]>totalMax) // 寻找最大得分并记录最大得分的序列的起始位置{totalMax=MAX[i][number];X=i;}}totalMin=totalMax;for (i=1;i<=number;i++){if (min[i][number]<totalMin) // 寻找最大得分并记录最大得分的序列的起始位置{totalMin=min[i][number];Y=i;}}}void StonesMerger::traceback(int **s,int i,int r) // 寻找轨迹{if (r==1){outputFile<<"A"<<(i>number ? i%number : i);}else if(r==2){outputFile<<"(A"<<(i>number ? i%number : i)<<"A"<<((i+1)>number ? (i+1)%number : (i+1))<<")";}else{outputFile<<"(";StonesMerger::traceback(s,i ,s[i][r]);StonesMerger::traceback(s,i+s[i][r],r-s[i][r]);outputFile<<")";}}// main,cpp#include "StonesMerger.h"int main(){StonesMerger sm;sm.run();return 0;}。
状态转移方程大全
1. 资源问题1-----机器分配问题总公司拥有高效生产设备M台,准备分给下属的N个公司。
各分公司若获得这些设备,可以为国家提供一定的盈利。
问:如何分配这M台设备才能使国家得到的盈利最大?求出最大盈利值。
其中M<=15,N<=10。
分配原则:每个公司有权获得任意数目的设备,但总台数不得超过总设备数M。
数据文件格式为:第一行保存两个数,第一个数是设备台数M,第二个数是分公司数N。
接下来是一个M*N的矩阵,表明了第I个公司分配J台机器的盈利。
用机器数来做状态,数组F[I,J]表示前I个公司分配J台机器的最大盈利。
则状态转移方程为:F[I,j]:=max(f[i-1,k]+w[i,j-k])2. 资源问题2------01背包问题有N件物品和一个容量为V的背包。
第i件物品的费用是c,价值是w。
求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
F[I,j]:=max(f[i-1,j-v[i]]+w[i],f[i-1,j]);3. 线性动态规划1-----朴素最长非降子序列设有由n个不相同的整数组成的数列,记为:a(1),a(2),……,a(n)且a(i)<>a(j) (i<>j)例如3,18,7,14,10,12,23,41,16,24。
若存在i1且有a(i1)则称为长度为e的不下降序列。
如上例中3,18,23,24就是一个长度为4的不下降序列,同时也有3,7,10,12,16,24长度为6的不下降序列。
程序要求,当原数列给出之后,求出最长的不下降序列。
F[i]:=max{f[j]+1}4. 剖分问题1-----石子合并在一个园形操场的四周摆放N堆石子(N≤100),现要将石子有次序地合并成一堆。
规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
编一程序,由文件读入堆数N及每堆的石子数(≤20)。
F[i,j]:=min(f[i,k]+f[k+1,j]+sum[i,j]);5. 剖分问题2-----多边形剖分多边形三角剖分是计算几何的一个几何基元.它可以简化问题规模,在计算机图形学、模式识别和地理数据库方面有重要应用.F[I,j]:=min(f[j,k]+f[k,j]+a[k]*a[j]*a[i]);6. 剖分问题3------乘积最大今年是国际数学联盟确定的“2000——世界数学年”,又恰逢我国著名数学家华罗庚先生诞辰90周年。
动态规划中的石子归并问题
动态规划中的⽯⼦归并问题⼀.有N堆⽯⼦,每堆的重量是w[i],可以任意选两堆合并,每次合并的花费为w[i]+w[j],问把所有⽯⼦合并成为⼀堆后的最⼩花费是多少。
因为是可以任意合并,所以每次合并的时候选最⼩的两堆合并,贪⼼即可。
⼆.有N堆⽯⼦,每堆的重量是a[i],排成⼀条直线,每次只能合并相邻的两堆,直到合成⼀堆为⽌,问最后的最⼩花费是多少。
分析:因为规定了只能合并相邻的两堆,显然不能使⽤贪⼼法。
分成⼦问题来考虑,定义dp[i][j]表⽰从第i的⽯⼦合并到第j个⽯⼦的最⼩花费,那么dp[1][N]就是问题的解。
可以推出dp[i][j] = min(dp[i][k]+dp[k+1][j]) k∈(i,j)初始时dp[i][j] = INF(i!=j) dp[i][i] = INF1 #include <iostream>2 #include <cstring>3 #include <cstdio>4 #include <string>5 #include <algorithm>6using namespace std;7int T, n;8int a[210], dp[210][210], sum[210], s[210][210];9//这⾥是数据量⽐较⼩10int INF = 99999999;11int main(){12while(scanf("%d", &n) != EOF){13 memset(sum, 0, sizeof(sum));14for(int i = 1; i <= n; i++){15 cin>>a[i];16 sum[i] = sum[i-1] + a[i];17 }18for(int i = 1; i <= n; i++){19for(int j = 1; j <= n; j++)20 dp[i][j] = INF;21 }22for(int i = 1; i <= n; i++) dp[i][i] = 0;2324for(int len = 2; len <= n; len++){ //表⽰归并的长度25for(int i = 1; i <= n-len+1; i++){ //归并的第⼀位26int j = i+len-1; //归并的最后⼀位27for(int k = i; k < j; k++){28 dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]);29 }30 }31 }32 cout<<dp[1][n]<<endl;33 }34return0;35 }这样复杂度是O(N^3)可以利⽤四边形不等式优化到O(N^2)四边形不等式:如果对于任意的a≤b≤c≤d,有m[a,c] + m[b,d] <= m[a,d] + m[b,c]那么m[i,j]满⾜四边形不等式。
(整理)深入分析区间型动态规划
深入分析区间型动态规划郑州市第九中学张旭祥区间型动态规划在信息学竞赛中应用甚广,它是动态规划中的经典问题,最小代价字母树是这类动态规划最经典的体现,对于初学者而言这类动态规划并不太好理解。
于是,区间型动态规划又成了动态规划中的难点问题。
下面,本文将为大家深入分析区间型动态规划的思想及其程序的实现,希望能对大家有所帮助。
历届大赛中区间型动态规划题目的考查。
区间型动态规划是各大信息竞赛出题的热点,具体体现在以下题目:1.合并石子----NOI19952.能量项链---NOIP20063.加分二叉树----NOIP20034.最优排序二叉树---- CTSC96这些题目出现的频次及其所在比赛的重要性足以说明区间型动态规划在各类动态规划中有着举足轻重的地位。
二、区间型动态规划的算法分析在这里就以经典的最小代价字母树作为例子,对区间型动态规划的算法进行分析。
问题描述:给定一个序列,如4、1、2、3我们将他们相加进行合并,最终合并成一个数,每次相加的代价是两个加数的和,求怎样的相加顺序可以使总代价最小。
很多初学者认为这类动态规划不易理解,其重要原因是这类动态规划与其他动态规划的思想不大相同,而初学者又是利用其他动态规划的思想来解决这类动态规划,从而进入了思维误区。
这种错误的思维模式一旦建立便很难重新建立正确的解题思想,从而陷入绝境。
这类动态规划正确的解法是这样的:首先,根据动态规划无后效性的性质可以想到:对于一个序列:A1、A2……An,假如最后相加的两个数是第一个数到第i个数的和S[1--i]以及第i+1个数到第n个数的和S[i+1--n],另外,对于第一个数到第i个数相加的最小代价是F[1,i]以及从第i+1到第n个数相加的最小代价为F[i+1,n],则总代价即为F[i+1,n]+F[1,i](前面相加的最小代价)+ S[1--i]+ S[i+1--n](最后一次相加的最小代价)。
由此,我们可以清楚的看出要想求出总代价的最小值只要枚举i的位置,使得F[i+1,n]+F[1,i] + S[1--i]+ S[i+1--n]的和最小即可。
在一个圆形操场的四周摆放着n堆石子现要将石子有次序地
在一个圆形操场的四周摆放着n堆石子。
现要将石子有次序地合并成一堆。
规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分,并分析算法的计算复杂性。
#include <stdio.h>#include <string.h>#defineN500#defineoo2000000000#defineMIN(a, b) (a)<(b)?(a):(b)#defineMAX(a, b) (a)>(b)?(a):(b)typedef struct { int c, d; } Node;int n;int v[N];//每堆石头的个数intsave[N];//输出最优解的具体合并需要随时改变v的值,所以为了同时输出最小,最大的合并,在完成一个任务之后需要回溯Node f[N][N];// f[i][j]存储最优解,同时存储合并线索int sum[N][N];// sum[i][j]表示从第i堆起,顺时针数j堆的石子总数void Print(int i, int j)//递归打印子序列f[i][j]的合并过程{int k, x;if(j != 1) {Print(i, f[i][j].d);//倒推子序列1的合并过程x = (i + f[i][j].d - 1)%n + 1;Print(x, j - f[i][j].d);//倒推子序列2的合并过程for(k = 1; k <= n; k++)//输出当前合并第i堆和第x堆的方案if(v[k] > 0) {if(i == k || x == k) printf("-%d ", v[k]);// -号表示这次操作合并该堆else printf("%d ", v[k]);}printf("\n");v[i] = v[i] + v[x];//新堆的大小v[x] = -v[x];//置为"-"类似于删除}}void Solve(int flag)// flag = 0求最小得分, flag = 1求最大得分{int i, j, k, x, t, result;for(i = 1; i <= n; i++)//仅含一堆石子的序列不存在合并f[i][1].c = f[i][1].d = 0; for(j = 2; j <= n; j++) {//顺推含2堆,3堆...n堆石子的各子序列的合并方案for(i = 1; i <= n; i++) {t = sum[i][j];if(flag == 0) f[i][j].c = oo;//求最小得分,那么需要初始化为ooelsef[i][j].c = 0;//求最大得分,那么需要初始化为0for(k = 1; k <= j-1; k++) { x = (i + k - 1)%n + 1;if((flag == 0 && f[i][k].c + f[x][j-k].c + t < f[i][j].c)||(flag != 0 && f[i][k].c + f[x][j-k].c + t > f[i][j].c)) {f[i][j].c = f[i][k].c + f[x][j-k].c + t;f[i][j].d = k;}}}}result = f[1][n].c; k = 1;//在子序列f[1][n], f[2][n]...f[n][n]中寻找最优值for(i = 2; i <= n; i++)if((flag == 0 && f[i][n].c < result) || (flag != 0 && f[i][n].c > result)){result = f[i][n].c;k = i;//记录下k}printf("%s score is : %d\n", flag == 0 ? "min" : "max", result);printf("合并过程如下:\n");Print(k, n);//由此k出发倒推合并过程printf("%d\n\n", sum[1][n]);//输出最后一次将石子合并成一堆的石子总数}int main(){int i, r, j, k, x, t;while(scanf("%d", &n), n) {for(i = 1; i <= n; i++) scanf("%d", &v[i]);//memset(sum, 0, sizeof(sum));memcpy(save+1, v+1, n*sizeof(v[1]));for(i = 1; i <= n; i++) sum[i][1] = v[i];for(j = 2; j <= n; j++)for(i = 1; i <= n; i++)sum[i][j] = v[i] + sum[i%n+1][j-1];Solve(0);memcpy(v+1, save+1, n*sizeof(v[1]));//回溯v以便求最大得分Solve(1);}return 0;}。
区间类型动态规划
4 * 3 2
样例分析
分析
• 我们先枚举第一次删掉的边,然后再对每种状 态进行动态规划求最大值 。 • 用f(i,j)表示从点i到点j进行删边操作所能得 到的最大值,num(i)表示第i个顶点上的数,若 为加法,那么:
f (i, j ) max ( f (k , i) f ( j k , i k ))
状态转移方程
• 设t[i,j]表示从第i堆到第j堆石子数总和。 Fmax(i,j)表示将从第i堆石子合并到第j堆石子的最大的得分 Fmin(i,j)表示将从第i堆石子合并到第j堆石子的最小的得分
F max( i, j ) max {F max( i 1, j ), F max( i, j 1)} t[i, j ]
Hale Waihona Puke 完美解决• 初始值 Fmax(i,i)=num(i) Fmin(i,i)=num(i) • 1<=i<=k=<j=n • 空间复杂度:O(n2) • 时间复杂度 : 先要枚举每一条边为 O(n) , 然后动态规划为O(n3 ),因此总为O(n4)。
当OP=‘*’
fmax(i, k ) * f max(k 1, j ) fmax(i, k ) * f min(k 1, j ) f max(i, j ) fmin(i, k ) * f max(k 1, j ) fmin(i, k ) * f min(k 1, j ) fmin(i, k ) * f min(k 1, j ) fmax(i, k ) * f min(k 1, j ) f min(i, j ) fmin(i, k ) * f max(k 1, j ) fmax(i, k ) * f max(k 1, j )
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
}
}
}
}
result = f[1][n].c; k = 1; //在子序列f[1][n], f[2][n]...f[n][n]中寻找最优值
for(i = 2; i <= n; i++)
if((flag == 0 && f[i][n].c < result) || (flag != 0 && f[i][n].c > result))
{
int k, x;
if(j != 1) {
Print(i, f[i][j].d); //倒推子序列1的合并过程
x = (i + f[i][j].d - 1)%n + 1;
Print(x, j - f[i][j].d); //倒推子序列2的合并过程
for(k = 1; k <= n; k++) //输出当前合并第i堆和第x堆的方案
if(v[k] > 0)
{
if(i == k || x == k) printf("-%d ", v[k]); // -号表示这次操作合并该堆
else printf("%d ", v[k]);
}printf("源自n");v[i] = v[i] + v[x]; //新堆的大小
v[x] = -v[x]; //置为"-"类似于删除
}
int main()
{
int i, r, j, k, x, t;
while(scanf("%d", &n), n) {
for(i = 1; i <= n; i++) scanf("%d", &v[i]);
//memset(sum, 0, sizeof(sum));
memcpy(save+1, v+1, n*sizeof(v[1]));
}
}
void Solve(int flag) // flag = 0求最小得分, flag = 1求最大得分
{
int i, j, k, x, t, result;
for(i = 1; i <= n; i++) //仅含一堆石子的序列不存在合并
f[i][1].c = f[i][1].d = 0;
for(j = 2; j <= n; j++) { //顺推含2堆,3堆...n堆石子的各子序列的合并方案
int save[N]; //输出最优解的具体合并需要随时改变v的值,所以为了同时输出最小,最大的合并,在完成一个任务之后需要回溯
Node f[N][N]; // f[i][j]存储最优解,同时存储合并线索
int sum[N][N]; // sum[i][j]表示从第i堆起,顺时针数j堆的石子总数
void Print(int i, int j) //递归打印子序列f[i][j]的合并过程
#define N 500
#define oo 2000000000
#define MIN(a, b) (a)<(b)?(a):(b)
#define MAX(a, b) (a)>(b)?(a):(b)
typedef struct { int c, d; } Node;
int n;
int v[N]; //每堆石头的个数
for(i = 1; i <= n; i++) sum[i][1] = v[i];
for(j = 2; j <= n; j++)
for(i = 1; i <= n; i++)
sum[i][j] = v[i] + sum[i%n+1][j-1];
Solve(0);
memcpy(v+1, save+1, n*sizeof(v[1])); //回溯v以便求最大得分
{
result = f[i][n].c;
k = i; //记录下k
}
printf("%s score is : %d\n", flag == 0 ? "min" : "max", result);
printf("合并过程如下:\n");
Print(k, n); //由此k出发倒推合并过程
printf("%d\n\n", sum[1][n]); //输出最后一次将石子合并成一堆的石子总数
x = (i + k - 1)%n + 1;
if((flag == 0 && f[i][k].c + f[x][j-k].c + t < f[i][j].c)
||(flag != 0 && f[i][k].c + f[x][j-k].c + t > f[i][j].c)) {
f[i][j].c = f[i][k].c + f[x][j-k].c + t;
for(i = 1; i <= n; i++) {
t = sum[i][j];
if(flag == 0) f[i][j].c = oo; //求最小得分,那么需要初始化为oo
else f[i][j].c = 0; //求最大得分,那么需要初始化为0
for(k = 1; k <= j-1; k++) {
在一个圆形操场的四周摆放着n堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分,并分析算法的计算复杂性。
#include <stdio.h>
#include <string.h>
Solve(1);}
return 0;
}