本质
将待求解问题分解为若干子问题,先求解子问题,再结合这些子问题的解得到原问题的解。(在这点上和分治法很像)
那为什么不直接用分治法:
因为重复计算。
适合用动态规划法求解的问题经分解得到的子问题往往不是相互独立的。
有些问题分解后的子问题往往是重复的,若用分支法则会重复计算耗费时间内存。
解决方法
用一个表来记录所有已解决的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。
步骤
用a[MAXN]储存输入的数据(一般是一维数组) 基于题目规则,找出最优解性质,刻画其结构特征,寻找关系式(状态转移方程)。(比如a数组总某些符合条件的数的和或者积) 基于关系式,用另一个数组来储存上面的得到的结果/最优值(比如f[MAXN][MAXN]二维数组) 自底向上的方式,递归或循环地定义最优值。 f的值一般是多次替换的。如下方法: 在符合条件的关系式中寻找最优值,然后填入f[i][j]。 或者定义f[i][j]为无穷大或无穷小,每次更迭(若有此次关系式所得出的值由于f[i][j],则替换)
例1
一组研究人员正在设计一项实验,以测试猴子的智商。他们将挂香蕉在建筑物的屋顶,同时,提供一些砖块给这些猴子。如果猴子足够聪明,它应当能够通过合理的放置一些砖块建立一个塔,并爬上去吃他们最喜欢的香蕉。 |
研究人员有n种类型的砖块,每种类型的砖块都有无限个。第i块砖块的长宽高分别用xi,yi,zi来表示。 同时,由于砖块是可以旋转的,每个砖块的3条边可以组成6种不同的长宽高。
在构建塔时,当且仅当A砖块的长和宽都分别小于B砖块的长和宽时,A砖块才能放到B砖块的上面,因为必须留有一些空间让猴子来踩。
你的任务是编写一个程序,计算猴子们最高可以堆出的砖块们的高度。
Input输入文件包含多组测试数据。
每个测试用例的第一行包含一个整数n,代表不同种类的砖块数目。n<=30.
接下来n行,每行3个数,分别表示砖块的长宽高。
当n= 0的时候,无需输出任何答案,测试结束。
Sample Input
1 10 20 30 2 6 8 10 5 5 5 7 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 5 31 41 59 26 53 58 97 93 23 84 62 64 33 83 27 0Output
对于每组测试数据,输出最大高度。格式:Case 第几组数据: maximum height = 最大高度
Sample Output
Case 1: maximum height = 40 Case 2: maximum height = 21 Case 3: maximum height = 28 Case 4: maximum height = 342题目大意:
堆箱子,下面的箱子必须长宽都比上面的大。最高能堆多高?
思路:
- 数据输入;
- 数据转化:每个箱子的数据转化为6组。如:abc abc bac bca cab abc
- 数据排序:按照长宽从小到大排序
- 初始化答案数组:result初始化为每组数据的高。
- 数据检索:从小到大对数据进行检索(for i),把在i之前所有比i箱子小的箱子result值进行对比,最大的值加到result[i]上。
- 结果输出:result数组的最大值即为所求。
代码:
/
Topic Name: Monkeyand Banana
Author: JHT(fw_25691)
Date: 2021-11-8
/
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 30 * 6;
//储存结果
int f[N];
//结构体node,储存每个箱子的长宽高xyh
struct node
{
int x, y, h;
}box[N];
//数据输入:一个箱子相当于有6个结构体的数据,index下标
int index;
void datain(int a, int b, int c)
{
box[index].x = a; box[index].y = b; box[index++].h = c;
box[index].x = b; box[index].y = a; box[index++].h = c;
box[index].x = a; box[index].y = c; box[index++].h = b;
box[index].x = c; box[index].y = a; box[index++].h = b;
box[index].x = b; box[index].y = c; box[index++].h = a;
box[index].x = c; box[index].y = b; box[index++].h = a;
}
//数据输入完毕,按照长宽对结构体进行排序
bool cmp(node a, node b)
{
return (a.x == b.x) ? (a.y < b.y) : (a.x < b.x);
}
int main()
{
int max1 = 0, max2;
int n;
int i, j;
int a, b, c;
int cnt = 0;
//cnt:数据组的下标
while (scanf("%d", &n) && n && ++cnt)
{
//index:数据组中数据的下标
index = 0;
//max1:当前最大结果
max1 = 0;
//数据输入
for (i = 0; i < n; i++)
{
cin >> a >> b >> c;
datain(a, b, c);
}
//数据排序
sort(box, box + index, cmp);
//将当前结果初始化为每个箱子的高
for (i = 0; i < index; i++)
{
f[i] = box[i].h;
}
//max2:在该箱子之前所有箱子的 符合条件的最大结果
for (i = 0; i < index; i++)
{
max2 = 0;
for (j = i; j >= 0; j--)
{
if (box[i].x > box[j].x && box[i].y > box[j].y)
{
max2 = max(max2, f[j]);
}
}
//当前结果f[i]更新为:(该箱子的高+max2)
f[i] += max2;
//max1记录最大结果
max1 = max(f[i], max1);
}
//输出答案
cout << "Case " << cnt << ": maximum height = " << max1 << endl;
}
}
例2
【题目描述】 |
设A和B是两个字符串。我们要用最少的字符操作次数,将字符串A转换为字符串B。这里所说的字符操作共有三种:
1、删除一个字符;
2、插入一个字符;
3、将一个字符改为另一个字符。
对任意的两个字符串A和B,计算出将字符串A变换为字符串B所用的最少字符操作次数。
【输入】第一行为字符串A;第二行为字符串B;字符串A和B的长度均小于2000。
【输入样例】
sfdqxbw gfdgw【输出】
只有一个正整数,为最少字符操作次数。
【输出样例】
4
题目大意:
求将A字符串转化为B字符串所需步骤的最小值。
思路:
- 建立i行j列数组,i是A的长度,j是B的长度。
- F[i][j]代表A的前i个对应转化成B的前j个所需的步骤数。
- 因为要求最少步骤,所以F[i][j]是(四周三个数字最小值)+1,而当A的第i个和B的第j个相同,那么毫无疑问,A的第i个对应转化成B的第j个是最省步骤的,而在此前提下,A的第i-1个对应转化成B的第j-1个是最省步骤的。所以此时F[i][j]=f[i-1][j-1];
代码:
/
Topic Name: 编辑距离
Author: JHT(fw_25691)
Date: 2021-11-8
/
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN = 2001;
int f[MAXN][MAXN] = { 0 };
//建立f二维数组,记录编辑距离
int main()
{
//定义和输入
int i, j;
char a[MAXN], b[MAXN];
cin >> &a[1];
getchar();
cin >> &b[1];
int len_a = strlen(a), len_b = strlen(b);
//len_a行,len_b列的数组
for (i = 1; i <= len_a; i++)
{
for (j = 1; j <= len_b; j++)
{
//如果a的i位置的字符==b的j位置的字符,则……
//否则该f=四周最低的数+1;
if (a[i] == b[j])
{
f[i][j] = f[i - 1][j - 1];
}
else
{
f[i][j] = min(f[i - 1][j - 1], min(f[i - 1][j], f[i][j - 1])) + 1;
}
}
}
//输出最后的f
cout << f[i - 1][j - 1];
}
例3
【题目描述】 |
由于在维护世界和平的事务中做出巨大贡献,Dzx被赠予糖果公司2010年5月23日当天无限量糖果免费优惠券。在这一天,Dzx可以从糖果公司的N件产品中任意选择若干件带回家享用。糖果公司的N件产品每件都包含数量不同的糖果。Dzx希望他选择的产品包含的糖果总数是K的整数倍,这样他才能平均地将糖果分给帮助他维护世界和平的伙伴们。当然,在满足这一条件的基础上,糖果总数越多越好。Dzx最多能带走多少糖果呢?
注意:Dzx只能将糖果公司的产品整件带走。
【输入】第一行包含两个整数N(1≤N≤100)和K(1≤K≤100)。
以下N行每行1个整数,表示糖果公司该件产品中包含的糖果数目,不超过1000000。
【输入样例】
5 7 1 2 3 4 5【输出】
符合要求的最多能达到的糖果总数,如果不能达到K的倍数这一要求,输出0。
【输出样例】
14
思路:
懒得写了,反正我代码注释写的挺详细了,直接看代码吧。
代码:
/
Topic Name: 拿糖果(法一)
Author: JHT(fw_25691)
Date: 2021-11-8
/
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN = 101;
//n行k列
int f[MAXN][MAXN];
int main()
{
int i, j, str[MAXN];
int n, k;
int state1, state2;
//输入数据
cin >> n >> k;
for (i = 1; i <= n; i++)
{
cin >> str[i];
}
//初始化f为-1,第0列为0。
memset(f, -1, sizeof(f));
for (i = 0; i < n; i++)
{
f[i][0] = 0;
}
//-1的意义:-1代表没有满足的条件。比如:若k=7,第一个给出的数据是5,则,第一行只有第5列有意义,以为第0行只有第0列!=-1.
//每一列代表的意义:当前糖果的余数。
int row;
for (i = 1; i <= n; i++)
{
for (j = 0; j < k; j++)
{
if (f[i - 1][j] != -1)
{
row = (j + str[i]) % k;
f[i][row] = max(f[i - 1][row], f[i - 1][j] + str[i]);
}
}
}
//输出最后一列第0行,即在最后结果中输出可以整除k的数。
cout << f[n][0];
}
</pre>
<strong>法二:</strong>
其实大同小异,网上大多是第一种方法,我自己写的时候发现不用这么绕,换了方式写一下,其实没差别,但是比较好理解8。
<pre>
/
Topic Name: 拿糖果(法二)
Author: JHT(fw_25691)
Date: 2021-11-8
/
#include<cstdio>
#include<iostream>
using namespace std;
const int MAXN = 101;
//n行k列
int f[MAXN][MAXN];
int main()
{
int i, j, str[MAXN];
int n, k;
int state1, state2;
//输入数据
cin >> n >> k;
for (i = 1; i <= n; i++)
{
cin >> str[i];
}
//初始化f为负极大值,第0列为0。
memset(f, 0, sizeof(f));
for (i = 1; i < k; i++)
{
f[0][i] = -100000;
}
//0的意义:每一个(str的数据)+0=(自身),这就是只拿自身的选项。
//负极大值的意义:如果前一列是负极大值,则代表目前所给出的糖果数量无论如何拼不出来余数为k的结果
int row;
for (i = 1; i <= n; i++)
{
for (j = 0; j < k; j++)
{
row = (k + j - str[i] % k) % k;
//当前结果更新为,该列上一列的值(代表本次糖果不拿),或者上一行列号为(k-本次糖果数)的值(代表本次糖果拿)。
f[i][j] = max(f[i - 1][j], f[i - 1][row] + str[i]);
}
}
//输出最后一列第0行,即在最后结果中输出可以整除k的数。
cout << f[n][0];
}
例4
【题目描述】 |
今年是国际数学联盟确定的"2000——世界数学年",又恰逢我国著名数学家华罗庚先生诞辰90周年。在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学智力竞赛的活动,你的一个好朋友XZ也有幸得以参加。活动中,主持人给所有参加活动的选手出了这样一道题目:
设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,找出一种分法,使得这K+1个部分的乘积最大。
同时,为了帮助选手能够正确理解题意,主持人还举了如下的一个例子:
有一个数字串:312, 当N=3,K=1时会有以下两种分法:
1)3*12=36
2)31*2=62
这时,符合题目要求的结果是:31*2=62。
现在,请你帮助你的好朋友XZ设计一个程序,求得正确的答案。
【输入】第一行共有2个自然数N,K(6≤N≤10,1≤K≤6)
第二行是一个长度为N的数字串。
【输入样例】
4 2 1231【输出】
输出所求得的最大乘积(一个自然数)。
【输出样例】
62思路:
略……
代码:
/
Topic Name: 乘积最大
Author: JHT(fw_25691)
Date: 2021-11-8
/
#include<cstdio>
#include<iostream>
using namespace std;
int f[20][20];
int str[20];
//从第left位到第right位连起来的数字,如mid_num(2,4)=234;
int mid_num(int left, int right)
{
int result = 0;
for (int i = left; i <= right; result = result * 10 + str[i], i++);
return result;
}
int main()
{
int i, j, y;
int n, k, str2[20];
long long num;
//输入数据,每个数字储存在数组str中
cin >> n >> k;
cin >> num;
for (i = 0; num; i++, num /= 10)
str2[i] = num % 10;
for (j = i - 1, y = 0; j >= 0; j--, y++)
str[y] = str2[j];
//i:i个数字; j:j个星号
//f[i][j]:i个数字用j个星号搭配的各种结果中最大的
memset(f, 0, sizeof(f));
for (i = 1; i <= n; i++)
{
f[i][0] = mid_num(0, i - 1);
for (j = 1; j <= i && j <= k; j++)
{
for (y = j; y < i; y++)
{
f[i][j] = max(f[i][j], f[y][j - 1] * mid_num(y, i - 1));
}
}
}
cout << f[i - 1][j - 1];
}
练题目:下面这个网址足矣。