原题详见UVa10003
本文主要包含一下内容:
1.数学模型的建立
2.递推伪代码的推导
3.程序实现
4.总结
关键字: 动态规划 集合
1.数学模型的建立
很多人解不出这道题从根本上是没有读懂题意。我们现在来从头分析一下。
已知:木棍的长度给定为L,切割点数给定为n,而且每个切割点的位置也是固定的。每次切割的花费等于被切割的木棍的长度。
要求:最少的花费
这一题的关键就是要理解每次切割的花费等于被切割的木棍的长度
怎么把这句话和切割这个动作联系起来呢?
我们现在定义(i,j)表示第i个切割点和第j个切割点所产生的木棍。
现在对这个木棍进行切割,假设切割点为第k个切割点,很显然 i<k<j
那么木棍(i,j)就变成了两根子木棍(i,k)(k,j),这次切割所产生的花费为c[j]-c[i] (注:c[i]表示第i个切割点距离左端点的距离)
定义d[i][j]为切割木棍(i,j)所需最小费用,那么:
d[i][j] = min{d[i][k]+d[k][j]+c[j]-c[i]} k:i+1->j-1
把左又端点分别看成第0个和第n+1个切割点,c[0] =0,c[n+1] =L
2.递推伪代码的推导:
一问:最初的状态是什么?
答:最初的状态是不能再切割的子木棍。何为不能再切割的子木棍呢?就是木棍(i,j)之间不再有切割点,
具体就是d[0][1] d[1][2] d[2][3] d[3][4] .......d[n][n+1],因为它们是不能再切割的,所以它们的初始值都是0.
接下来应该计算的就是能被切割一刀的子木棍
具体是的d[0][2] d[1][3] ........ d[n-1][n+1]
接下来是 d[0][3] d[1][4] ......... d[n-2][n+1]
最后的结果是d[0][n+1]
因此 i,j里包含的切割点数p是第一层循环 p:1->n
i是第二层循环,i:0->n
k是第三层循环 k:i+1->j-1
伪代码如下:
memset(d,0,sizeof(d));
for p:1->n
do for i:0->n
j = i+p;
if j>n+1 break
for k:i+1->j-1
d[i][j] = min{d[i][k]+d[k][j]+c[j]-c[i]}
3.具体代码实现
#include<iostream>
using namespace std;
int n;
int L;
int f[50][50];
int c[50];
const int INF = 99999;
int main(){
cin >> L;
cin >> n;
c[0] = 0;
c[n + 1] = L;
for (int i = 1; i <n+1; i++)
cin >> c[i];
memset(f, 0, sizeof(f));
for (int p = 1; p <= n + 1; p++){
for (int i = 0; i <= n + 1; i++){
int j = i + p;
if (j > n + 1) break;
int min = INF;
for (int k = i + 1; k < j; k++){
int t = f[i][k] + f[k][j] + c[j] - c[i];
if (t < min) min = t;
}
if (min != INF)f[i][j] = min;
}//for i
}//for p
cout << f[0][n + 1];
}
4.总结
大多数的不会做在于根本没有读懂题。
大多数逻辑上的bug在于没有读懂题。
读题读题读题。理解理解理解。
从这题上总结了一下如何写根据状态转移方程写递推程序。
写出状态转移方程和写出具体程序是两回事情,写出状态转移方程到具体程序实现之间还隔着一个伪代码的过程。这个过程对于逻辑复杂的题目很重要!
所以,读题+写伪代码!