本文主要是介绍动态规划(3):熟练度练习(POJ 1458、最佳加法表达式、bailian2755、POJ3624、bailian1088),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
最长公共子序列
Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 47599 | Accepted: 19562 |
Description
Input
Output
Sample Input
abcfbc abfcab
programming contest
abcd mnp
Sample Output
4
2
0
Source
线性DP基础模型 (下面会着重讲线性DP和区间DP 具体是什么到时候会讲)
输入两个串s1,s2,
设MaxLen(i,j)表示:
s1的左边i个字符形成的子串,与s2左边的j个字符形成的子串的最长公共子序列的长度(i,j从0开始算)
MaxLen(i,j) 就是本题的“状态”
假定 len1 = strlen(s1),len2 = strlen(s2)
那么题目就是要求 MaxLen(len1,len2)
套用基本思路中的定义, 我们来大致说明一下上面子问题的正确定(无严格证明)
首先我们要求字符串s1(长度为len1), s2(长度为len2)的最长公共子序列, 也就是求(len1, len2)个字符的最长公共子串。我们将问题拆分成求(i, j)个字符的最长公共子串。
正如前文说的, 他需要满足三个条件这个拆分才是成立的:具有相同的子问题*满足最优子结构*无后效性
(1)具有相同子问题:(len1, len2)可以转化为(len1 - 1, len2)和(len1, len2 - 1), 这样依次划分下去最终的问题划分结果就是(0, 0)这显然是一个可以解决的最终分割情况。
(2)满足最优子结构:我们已知(0, 0)的值是0, 这一定是最优的结构, 那么我们设(i-1, j)、(i, j-1)和(i-1, j-1)都是最优子结构, 对于(i,j)的值, 我们可以通过查看(str1[i], str2[j])的关系得来, 与之前的(i-1,j-1)无关
(3) 无后效性:每一步我们只考虑各自的(i,j)个节点, 对于后面节点的选择与前面节点的选择方式无关(这里的影响是指:在求(i,j+2)的时候与(i,j+1)以及之前的节点的选择方式无关)
(4)此题特别需要证明的一点:S1[i-1]!= s2[j-1]时,MaxLen(S1,S2)不会比MaxLen(S1,S2j-1)和MaxLen(S1i-1,S2)两者之中任何一个小,也不会比两者都大。
递推公式:
if ( s1[i-1] == s2[j-1] ) //s1的最左边字符是s1[0]MaxLen(i,j) = MaxLen(i-1,j-1) + 1;//这里的证明暂时不证
elseMaxLen(i,j) = Max(MaxLen(i,j-1),MaxLen(i-1,j) );
边界情况:
MaxLen(n,0) = 0 ( n= 0…len1)
MaxLen(0,n) = 0 ( n=0…len2)
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define maxn 1010char str1[maxn];
char str2[maxn];
int maxlen[maxn][maxn];using namespace std;int main(void)
{while (cin >> str1 >> str2){int length1 = strlen(str1);int length2 = strlen(str2);int ntemp;int i, j;for (i = 1; i <= length1; i++)maxlen[i][0] = 0;for (j = 0; j <= length2; j++)maxlen[0][j] = 0;for (int i = 1; i <= length1; i++){for (int j = 1; j <= length2; j++){if (str1[i - 1] == str2[j - 1])maxlen[i][j] = maxlen[i - 1][j - 1] + 1;elsemaxlen[i][j] = max(maxlen[i - 1][j], maxlen[i][j - 1]);}}printf("%d\n", maxlen[length1][length2]);}return 0;
}
最佳加法表达式
假定数字串长度是n,添完加号后,表达式的最后一个加号添加在第 i 个数字后面,那么整个表达式的最小值,就等于在前 i 个数字中插入 m – 1个加号所能形成的最小值,加上第 i + 1到第 n 个数字所组成的数的值(i从1开始算)。
对于这样的一道题, 我们用如下的二元状态来形容(n,m)代表在n个数中插入m个加号(这满足前三种要求, 可以自己证明一下, 其实在证明的时候只需证明“无后效性”)
我们从后往前看, 假设我们在第i,j之间放置加号 那么(n,m) = (i,m - 1) + num(j, n);翻译一下就是[j,n]形成的数 + 前i个数中插入m-1个加号形成的最大值。(这的(n,m)仅仅是用来说明递归关系, 并不一定是正确值, 完整方程在下面)
if m = 0V(n,m) = n个数字构成的整数
else if n < m + 1V(n,m) = ∞
elseV(n,m) = Min{ V(i,m-1) + Num(i+1,n) } ( i = m … n-1);
递归代码(可以自己尝试一下递推, 只要将这个递归回溯的过程单独拿出来, 就能构造成我为人人型递归)
#include<cstdio>
#include<cstring>
#include<algorithm>using namespace std;long long num[110][110];
int number[100];
long long mark[100][100];long long V(int n, int m)//n个数 m个加号
{//printf("%d %d\n", n, m);if (m == 0)return num[1][n];else if (n == 0)return 0xfffffff;else{if (mark[n][m] != 0)return mark[n][m];long long fin = 0xfffffff;for (int i = m; i <= n - 1; i++)fin = min(fin, V(i, m - 1) + num[i + 1][n]);mark[n][m] = fin;return fin;}
}int main(void)
{int n, m;while (scanf("%d %d", &n, &m) != EOF)//n个数, m个加号{for (int i = 1; i <= n; i++)scanf("%d", &number[i]);for (int i = 1; i <= n; i++){num[i][i] = number[i];for (int j = i + 1; j <= n; j++){num[i][j] = num[i][j - 1] * 10 + number[j];//printf("%d %d %lld\n", i, j, num[i][j]);}}memset(mark, 0, sizeof(mark));printf("%lld\n", V(n, m));}return 0;
}
bailian2755
总时间限制: 10000ms 内存限制: 65536kB
描述
有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。
输入
输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的数目。接下来的n行,每行有一个1到40之间的正整数,分别给出a1,a2……an的值。
输出
输出不同的选择物品的方式的数目。
样例输入
3
20
20
20
样例输出
3
这里基本可以看作是一种套路, 属于背包的套路。背包的基本子问题划分方式就是对于第i项物品选还是不选。在背包问题中选与不选就导致了两种状态之间不同的转移方式(1)dp[k] = max(dp[k], dp[k-v[i]] + s[i]);
这里max中的dp[k]对应的就是不选择第i项物品的情况, v[k]就是付出的价值s[i]就是因为选择这个物品而得到的价值。所以dp[k - v[i]] + s[i]就对应着选择地i种物品的情况。然后再这两者之间求最大值就是背包问题的基本思路。
在这里是对标准背包问题的一个小小的变形, 在这里我们考虑的是最终装满背包的总方案数, 这里在求dp[i]中的max函数也就应该换乘‘+’(因为在这里并不是在求多个路径中最优的那一个, 而是不同路径的个数, 自然应该在不同节点统计不同的路径数)
状态转移方程:
dp[i] = sum(dp[i - v[K]]), ( K = 1,2,3 …… n) && (i > v[K])
#include <iostream>
#define MAX 41
using namespace std;int main()
{ int n,i,j,input; int sum[MAX];memset(sum, 0, sizeof(sum));cin >> n; sum[0] = 1;for(i=0;i<n;i++){ cin >> input; for (j = 40; j >= 1; j--){if(j - input >= 0)sum[j] += sum[j - input];}} cout << sum[40] << endl; return 0;
}
POJ 3624
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 33379 | Accepted: 14790 |
Description
Bessie has gone to the mall’s jewelry store and spies a charm bracelet. Of course, she’d like to fill it with the best charms possible from the N (1 ≤ N ≤ 3,402) available charms. Each charm i in the supplied list has a weight Wi (1 ≤ Wi ≤ 400), a ‘desirability’ factor Di (1 ≤ Di ≤ 100), and can be used at most once. Bessie can only support a charm bracelet whose weight is no more than M (1 ≤ M ≤ 12,880).
Given that weight limit as a constraint and a list of the charms with their weights and desirability rating, deduce the maximum possible sum of ratings.
Input
* Line 1: Two space-separated integers: N and M
* Lines 2..N+1: Line i+1 describes charm i with two space-separated integers: Wi and Di
Output
* Line 1: A single integer that is the greatest sum of charm desirabilities that can be achieved given the weight constraints
Sample Input
4 6 1 4 2 6 3 12 2 7
Sample Output
23
Source
分析
标准的01背包递推: F[i][j] = max(F[i-1][j],F[i-1][j-w[i]]+d[i])边界:if (w[1] <= j)F[1][j] = d[1];elseF[1][j] = 0;
注意这道题唯一的坑点就在于数据量过大导致没有足够的空间来存放二维数组, 注意到这个二维数组的下一行的值,只用到了上一行的正上方及左边的值,因此可用滚动数组的思想,只要一行即可。即可以用一维数组,用“人人为我”递推型动归实现。
#include<cstdio>
#include<cstring>
#include<algorithm>using namespace std;int dp[13010];
int w[3510], d[3510];int main(void)
{int n, m;while (scanf("%d %d", &n, &m) != EOF){for (int i = 1; i <= n; i++){scanf("%d %d", &w[i], &d[i]);}memset(dp, 0, sizeof(dp));for (int i = 1; i <= n; i++){for (int j = m; j >= w[i]; j--){dp[j] = max(dp[j], dp[j - w[i]] + d[i]);}}printf("%d\n", dp[m]);}return 0;
}
·
·
·
滑雪
总时间限制: 1000ms 内存限制: 65536kB
描述
Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-…-3-2-1更长。事实上,这是最长的一条。
输入
输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。
输出
输出最长区域的长度。
样例输入
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
样例输出
25
递推公式
L(x, y) = max(L(x - 1, y), L(x + 1, y), L(x, y - 1), L(x, y + 1)) + 1;
分析:
1) “人人为我”式递推
L(i,j)表示从点(i,j)出发的最长滑行长度。 一个点(i,j), 如果周围没有比它低的点,L(i,j) = 1
将所有点按高度从小到大排序。每个点的 L 值都初始化为1 从小到大遍历所有的点。经过一个点(i,j)时,用递推公式求L(i,j)
2)“我为人人”式递推
L(i,j)表示从点(i,j)出发的最长滑行长度。 一个点(i,j), 如果周围没有比它低的点,L(i,j) = 1
将所有点按高度从小到大排序。每个点的 L 值都初始化为1 从小到大遍历所有的点。经过一个点(i,j)时,要更新他周围的,比它高的点的L值。
代码(我为人人形式)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>using namespace std;struct node {int x;int y;int heigh;node(int a = 0, int b = 0, int c = 0) :x(a), y(b), heigh(c) {}friend bool operator < (node a, node b){return a.heigh > b.heigh;}
}map[110][110];
int n, m;
int dir[4][2] = { {-1,0}, {1,0}, {0,1}, {0,-1} };
int dp[110][110];int main(void)
{while(scanf("%d %d", &n, &m) != EOF){priority_queue<node>Q;int maxx = 1;for (int i = 1; i <= n; i++){for (int j = 1; j <= m; j++){scanf("%d", &map[i][j].heigh);map[i][j].x = i;map[i][j].y = j;Q.push(node(i, j, map[i][j].heigh));dp[i][j] = 1;}}while (!Q.empty()){node a = Q.top();Q.pop();for (int i = 0; i < 4; i++){int xx = a.x + dir[i][0];int yy = a.y + dir[i][1];if (xx <= 0 || xx > n || yy <= 0 || yy > m)continue;if (map[xx][yy].heigh <= map[a.x][a.y].heigh)continue;dp[xx][yy] = max(dp[xx][yy], dp[a.x][a.y] + 1);maxx = max(maxx, dp[xx][yy]);}}printf("%d\n", maxx);}return 0;
}
这篇关于动态规划(3):熟练度练习(POJ 1458、最佳加法表达式、bailian2755、POJ3624、bailian1088)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!