动态规划
入门 DP入门参考:漫画:什么是动态规划?
进阶参考:【DP专辑】ACM动态规划总结
http://www.cnblogs.com/raichen/p/5772056.html
动态规划原理
1-最优子结构
用动态规划求解最优化问题的第一步就是刻画最优解的结构,如果一个问题的解结构包含其子问题的最优解,就称此问题具有最优子结构性质。因此,某个问题是否适合应用动态规划算法,它是否具有最优子结构性质是一个很好的线索。使用动态规划算法时,用子问题的最优解来构造原问题的最优解。因此必须考查最优解中用到的所有子问题。
2-重叠子问题
在斐波拉契数列,可以看到大量的重叠子问题,比如说在求fib(6)的时候,fib(2)被调用了5次。如果使用递归算法的时候会反复的求解相同的子问题,不停的调用函数,而不是生成新的子问题。如果递归算法反复求解相同的子问题,就称为具有重叠子问题(overlapping subproblems)性质。在动态规划算法中使用数组来保存子问题的解,这样子问题多次求解的时候可以直接查表不用调用函数递归。
3-无后效性
将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。
数塔系列 首先什么是“数塔类型”?从某一点转向另一点或者说是从某一状态转向另一状态,有多种选择方式(比如这里的9->12 , 9->15),从中选取一条能产生最优值的路径。
参考:
动态规划“数塔”类型题目总结
数塔问题 :要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
数塔问题的经典解法是从后面算到前面,如果从前面算到后面很麻烦,因为后面一层有很多个数字是由少到多,如果反过来由多到少就简单了
分析:站在位置9,我们可以选择沿12方向移动,也可以选择沿着15方向移动,现在我们假设“已经求的”沿12方向的最大值x和沿15方向的最大值y,那么站在9的最大值必然是:Max(x,y) + 9。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> #include <algorithm> using namespace std;const int MAXN = 111 ;int Map[MAXN][MAXN];int main () { ios::sync_with_stdio (false ); int test; cin >> test; while (test--) { int n; cin >> n; for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= i; j++) cin >> Map[i][j]; for (int i = n - 1 ; i >= 1 ; i--) { for (int j = 1 ; j <= i; j++) { Map[i][j] = Map[i][j] + max (Map[i + 1 ][j], Map[i + 1 ][j + 1 ]); } } cout << Map[1 ][1 ] << endl; } return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 第0秒 5 (这里的数字指的是第N秒可能到达的位置坐标) 第1秒 4 5 6 第2秒 3 4 5 6 7 第3秒 2 3 4 5 6 7 8 第4秒 1 2 3 4 5 6 7 8 9 第5秒 0 1 2 3 4 5 6 7 8 9 10 第6秒 0 1 2 3 4 5 6 7 8 9 10 第7秒 ................. 可以发现从第5秒开始之后就都是 0 1 2 3 4 5 6 7 8 9 10
和“数塔”一样,它也是从某一点出发,有多个选择的问题(往前走一步,呆在原地,往后走一步)从中选择一条最优值路径(获得馅饼最多)。还是按照“数塔”的思考方式,我们可以假设“已经求得”下一个站在位置4获得的最大值x和呆在原地获得的最大值y以及站在位置6获得的最大值z,那么对于起始位置5获得最大值就是Max(x,y,z) ,因此可以得到状态转移方程为:
1 dp[t][x] += max (max (dp[t + 1 ][x - 1 ], dp[t + 1 ][x + 1 ]),dp[t + 1 ][x]);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int MAXN = 110000 ;int dp[MAXN][20 ];int main () { int n, Max, t, x; while (cin >> n && n) { Max = 0 ; memset (dp, 0 , sizeof (dp)); while (n--) { cin >> x >> t; if (t > Max) Max = t; dp[t][x]++; } for (t = Max - 1 ; t >= 0 ; t--) { dp[t][0 ] += max (dp[t+1 ][0 ],dp[t+1 ][1 ]); for (x = 1 ; x < 10 ; x++) { dp[t][x] += max ( max (dp[t + 1 ][x - 1 ],dp[t + 1 ][x]), dp[t + 1 ][x + 1 ]); } dp[t][10 ] += max (dp[t + 1 ][9 ], dp[t + 1 ][10 ]); } cout << dp[0 ][5 ] << endl; } return 0 ; }
1 2 3 4 5 6 依然和“数塔”一样,从某一点出发,面临多个选择(往上,往左,往下,往右)从中选择一条最优值路径(滑雪距离最长) 若对A点求,很显然它的最大值就为: Max (上,右,下,左) + 1 因此对于任意位置[i,j], 其状态转移方程为:m[i][j] = Max (m[i-1 ][j] , m[i][j+1 ] , m[i+1 ][j] , m[i][j-1 ]) + 1 由于这道题很难画出它的路径图(起点和终点都不知道)因此很难用“列表格”的方式自底向上求解。 解题思路:设一与输入数组对应的状态数组 dp,其值代表输入数组中此点的最长滑雪路径。使用广搜填表,最后遍历输出最大值即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 #include <iostream> #include <cstring> using namespace std;int row, col; const int MAX = 1e2 + 10 ;int Map[MAX][MAX];int dp[MAX][MAX]; int check (int i, int j) { return (i >= 1 && j >= 1 && i <= row && j <= col); } int BFS (int i, int j) { int Max = 0 ; if (check (i, j + 1 ) && Map[i][j] > Map[i][j + 1 ]) { if (dp[i][j + 1 ]) Max = Max > dp[i][j + 1 ] ? Max : dp[i][j + 1 ]; else { Max = Max > BFS (i, j + 1 ) ? Max : BFS (i, j + 1 ); } } if (check (i + 1 , j) && Map[i][j] > Map[i + 1 ][j]) { if (dp[i + 1 ][j]) { Max = Max > dp[i + 1 ][j] ? Max : dp[i + 1 ][j]; } else { Max = Max > BFS (i + 1 , j) ? Max : BFS (i + 1 , j); } } if (check (i, j - 1 ) && Map[i][j] > Map[i][j - 1 ]) { if (dp[i][j - 1 ]) { Max = Max > dp[i][j - 1 ] ? Max : dp[i][j - 1 ]; } else { Max = Max > BFS (i, j - 1 ) ? Max : BFS (i, j - 1 ); } } if (check (i - 1 , j) && Map[i][j] > Map[i - 1 ][j]) { if (dp[i - 1 ][j]) { Max = Max > dp[i - 1 ][j] ? Max : dp[i - 1 ][j]; } else { Max = Max > BFS (i - 1 , j) ? Max : BFS (i - 1 , j); } } if (Max == 0 ) dp[i][j] = 1 ; else dp[i][j] = 1 + Max; return dp[i][j]; } int main () { ios::sync_with_stdio (false ); while (cin >> row >> col) { memset (dp, 0 , sizeof (dp)); for (int i = 1 ; i <= row; i++) for (int j = 1 ; j <= col; j++) cin >> Map[i][j]; int ans = 0 ; for (int i = 1 ; i <= row; i++) for (int j = 1 ; j <= col; j++) { int tmp = BFS (i, j); if (tmp > ans) ans = tmp; } cout << ans << endl; } return 0 ; }
数位DP 数位dp是一种计数用的dp,一般就是要统计一个区间内满足一些条件数的个数。
参考:
数位dp总结 之 从入门到模板
数位dp记录——bili
题目大意:在[0,n]的范围内存在多少个数字含有49
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <iostream> #include <cstring> using namespace std;typedef long long ll;int digit[20 ]; ll dp[20 ][2 ]; ll dfs (ll len, bool if_num, bool limit) { if (len == 0 ) return 1 ; if (!limit && dp[len][if_num]) return dp[len][if_num]; ll cnt = 0 , up = (limit ? digit[len] : 9 ); for (int i = 0 ; i <= up; i++) { if (if_num && i == 9 ) continue ; cnt += dfs (len - 1 , i == 4 , limit && i == up); } if (!limit) dp[len][if_num] = cnt; return cnt; } ll solve (ll num) { int k = 0 ; while (num) { digit[++k] = num % 10 ; num /= 10 ; } return dfs (k, false , true ); } int main () { ios::sync_with_stdio (false ); ll n; cin >> n; while (n--) { memset (dp, 0 , sizeof (dp)); ll num; cin >> num; cout << num + 1 - solve (num) << endl; } return 0 ; }
在HDU3555基础上面多了一个区间,开始的时候是给出最大值m,区间是1~m,现在是区间是n~m,那么只要solve(m) - solve(n - 1)就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <iostream> #include <cstring> using namespace std;typedef long long ll;int digit[20 ];ll dp[20 ][2 ]; ll dfs (ll len, bool if_num, bool limit) { if (len == 0 ) return 1 ; if (!limit && dp[len][if_num]) return dp[len][if_num]; ll cnt = 0 , up = (limit ? digit[len] : 9 ); for (int i = 0 ; i <= up; i++) { if (if_num && i == 2 ) continue ; if (if_num ==4 || i == 4 ) continue ; cnt += dfs (len - 1 , i == 6 , limit && i == up); } if (!limit) dp[len][if_num] = cnt; return cnt; } ll solve (ll num) { int k = 0 ; while (num) { digit[++k] = num % 10 ; num /= 10 ; } return dfs (k, false , true ); } int main () { ios::sync_with_stdio (false ); ll n, m; while (cin >> n >> m && n && m) { memset (dp, 0 , sizeof (dp)); cout << solve (m) - solve (n - 1 ) << endl; } return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> #include <cstring> using namespace std;typedef long long ll; int digit[20 ];ll dp[20 ][2 ]; ll dfs (ll len, bool if_num, bool limit) { if (len == 0 ) return if_num; if (!limit && dp[len][if_num]) return dp[len][if_num]; ll cnt = 0 , up = (limit ? digit[len] : 9 ); for (int i = 0 ; i <= up; i++) cnt += dfs (len - 1 , if_num || i == 6 , limit &&i == digit[len]); if (!limit) dp[len][if_num] = cnt; return cnt; } ll solve (ll num) { int k = 0 ; while (num) { digit[++k] = num % 10 ; num /= 10 ; } return dfs (k, false , true ); } int main () { ios::sync_with_stdio (false ); ll n, m; while (cin >> n >> m) { memset (dp, 0 , sizeof (dp)); cout << solve (m) - solve (n - 1 ) << endl; } return 0 ; }
LCIS—最长上升公共子序列 讲解:
zoj2432 hdoj1423 最长公共上升子序列(LCIS)
LCIS 最长公共上升子序列问题DP算法及优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> using namespace std;const int MAXN = 1E3 ;int s1[MAXN], s2[MAXN];int len1, len2;int LCIS () { int ans = 0 ; int dp[MAXN][MAXN] = {0 }; for (int i = 0 ; i < len1; i++) { int maxdp = 0 ; for (int j = 0 ; j < len2; j++) { if (s1[i] != s2[j]) dp[i + 1 ][j] = dp[i][j]; if (s1[i] > s2[j] && maxdp < dp[i + 1 ][j]) maxdp = dp[i + 1 ][j]; if (s1[i] == s2[j]) dp[i + 1 ][j] = maxdp + 1 ; if (dp[i + 1 ][j] > ans) ans = dp[i + 1 ][j]; } } return ans; } int main () { ios::sync_with_stdio (false ); int test; cin >> test; while (test--) { cin >> len1; for (int i = 0 ; i < len1; i++) cin >> s1[i]; cin >> len2; for (int i = 0 ; i < len2; i++) cin >> s2[i]; cout << LCIS () << endl; if (test) cout << endl; } return 0 ; }
LIS—最长递增子序列 问题描述:找出一个n个数的序列的最长单调递增子序列: 比如A = {5,6,7,1,2,8}
的LIS是5,6,7,8
O(n^2)
的复杂度最优子结构
假设LIS[i]
是以arr[i]
为末尾的LIS序列的长度。则:
LIS[i] = {1+Max(LIS(j))}
; j<i, arr[j]<arr[i]
;
LIS[i] = 1, j<i
, 但是不存在arr[j]<arr[i]
;
所以问题转化为计算Max(LIS(j))
0<i<n
重叠的子问题
以arr[i] (1<= i <= n)
每个元素结尾的LIS序列的值是 重叠的子问题。
所以填表时候就是建立一个数组DP[i]
, 记录以arr[i]
为序列末尾的LIS长度。
DP[i]怎么计算?
遍历所有j<i
的元素,检查是否DP[j]+1>DP[i] && arr[j]<arry[i]
若是,则可以更新DP[i]
图示
arr[1]到arr[9]存值
LIS代表此元素到arr[1]的LIS
LIS来源代表是从哪个继承而来,值为数组下标
HDU1257(最少拦截系统)模板题
http://acm.hdu.edu.cn/showproblem.php?pid=1257
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> #include <cstring> using namespace std;int LIS (int arr[], int len) { const int dp_len = len + 10 ; int *dp = new int [dp_len]; memset (dp, 0 , sizeof (dp[0 ]) * dp_len); arr[0 ] = -1e9 ; for (int i = 1 ; i <= len; i++) for (int j = 0 ; j < i; j++) { if (arr[i] > arr[j] && dp[i] < dp[j] + 1 ) dp[i] = dp[j] + 1 ; } int MAX = 0 ; for (int i = 1 ; i <= len; i++) { if (dp[i] > MAX) MAX = dp[i]; } delete [] dp; return MAX; } int main () { ios::sync_with_stdio (false ); int n; while (cin >> n) { int *arr = new int [n + 10 ]; for (int i = 1 ; i <= n; i++) cin >> arr[i]; cout << LIS (arr, n) << endl; } return 0 ; }
参考:
算法设计 - LCS 最长公共子序列&&最长公共子串 &&LIS 最长递增子序列
【算法知识总结】最长递增子序列
【算法设计指南】最长递增子序列(微博/微信公众号:@算法时空—bilibili)
nlgn
的复杂度参考:
HRBU ACM 01背包 LIS 拓扑 凸包—-bilibili——20:41
这个比较难讲,看视频比较好理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> #include <algorithm> #include <cstring> using namespace std;int LIS (int arr[], int len) { int *dp = new int [len + 10 ](); int ans = 0 ; for (int i = 1 ; i <= len; i++) { int pos = lower_bound (dp + 1 , dp + ans + 1 , arr[i]) - dp; dp[pos] = arr[i]; ans = max (ans, pos); } return ans; } int main () { ios::sync_with_stdio (false ); int n; while (cin >> n) { int *arr = new int [n + 10 ](); for (int i = 1 ; i <= n; i++) cin >> arr[i]; cout << LIS (arr, n) << endl; } return 0 ; }
图示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <cstdio> #include <cmath> using namespace std;typedef long long ll;const int MAXN = 1E5 +10 ;int arr[MAXN], dp[MAXN] = {0 }, rdp[MAXN] = {0 };int main () { int n, m; scanf ("%d %d" , &n, &m); for (int i = 1 ; i <= n; i++) scanf ("%d" , &arr[i]); for (int i = 1 ; i <= n; i++) { if (arr[i-1 ] >= arr[i]) dp[i] = dp[i-1 ] + 1 ; else dp[i] = 1 ; } for (int i = n; i >= 1 ; i--) { if (arr[i+1 ] >= arr[i]) rdp[i] = rdp[i+1 ] + 1 ; else rdp[i] = 1 ; } int n1, n2; while (m--) { scanf ("%d %d" , &n1, &n2); if (dp[n2] + rdp[n1] >= n2-n1+1 ) printf ("Yes\n" ); else printf ("No\n" ); } return 0 ; }
最大递增子数组和 这个是由LIS O(n2)的办法变化而来的,因为O(nlgn)的那个代码的dp数组虽然保存的是一个最长递增序列,但是里面是最小的,不是最大的,所以只能由n2那个办法转变而来
这个动态规划可以先看前三项
dp[1] = num[1]
num[2]如果大于num[1]
dp[2] = dp[1] + num[2]
否则dp[2] = num[2]
num[3]如果大于num[2]
如果num[3]大于num[1]
如果num[3]不大于num[2]同时也不大于num[1]
说了这么多其实就是一个意思,用之前的每一个数和当前的数比较,比当前数小的就加上dp【之前数】,不然就等于当前数
参考:动态规划:HDU1087-Super Jumping! Jumping! Jumping!(最大上升子序列和)
HDU1087(Super Jumping! Jumping! Jumping!)模板题
http://acm.hdu.edu.cn/showproblem.php?pid=1087
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> #include <cstring> #include <algorithm> using namespace std;int LIS (int arr[], int len) { const int dp_len = len + 10 ; int * dp = new int [dp_len](); arr[0 ] = -1e9 ; int MAX = -1e9 ; for (int i = 1 ; i <= len; i++) { for (int j = 0 ; j <= i; j++) { if (arr[i] > arr[j]) dp[i] = max (dp[i], dp[j] + arr[i]); } dp[i] = max (dp[i], arr[i]); if (dp[i] > MAX) MAX = dp[i]; } delete [] dp; return MAX; } int main () { ios::sync_with_stdio (false ); int n; while (cin >> n && n) { int *arr = new int [n + 10 ](); for (int i = 1 ; i <= n; i++) cin >> arr[i]; cout << LIS (arr, n) << endl; } return 0 ; }
LCS—最长公共子序列 找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的。而最长公共子序列则并不要求连续。
cnblogs与belong,最长公共子序列为blog(cnblogs, belong),最长公共子串为lo(cnblogs, belong)这两个问题都是用空间换空间,创建一个二维数组来记录之前的每个状态
解释参考:LCS(最长公共子序列)注意:是可以不连续的,区别于最长公共子串
poj1458(Common Subsequence)模板题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int MAXDP = 1e3 ;const int MAXS = 1e7 ;int dp[MAXDP][MAXDP];char s1[MAXS], s2[MAXS];int LCS (char * s1, char * s2) { int len1 = strlen (s1) - 1 ; int len2 = strlen (s2) - 1 ; for (int i = 0 ; i <= len1; i++) for (int j = 0 ; j <= len2; j++) { if (i == 0 || j == 0 ) dp[i][j] = 0 ; else if (s1[i] == s2[j]) dp[i][j] = dp[i - 1 ][j - 1 ] + 1 ; else dp[i][j] = max (dp[i - 1 ][j], dp[i][j - 1 ]); } return dp[len1][len2]; } int main () { s1[0 ] = ' ' , s2[0 ] = ' ' ; ios::sync_with_stdio (false ); while (cin >> s1 +1 >> s2 + 1 ) cout << LCS (s1, s2) << endl; return 0 ; }
1 可以用pre[i][j]来记录dp[i][j]状态转移时是从哪种状态转移过来的,一共就三种:dp[i−1][j−1]+1,dp[i−1][j],dp[i][j−1]。然后从dp[len0][len1]开始往前递归,遇到从dp[i−1][j−1]+1状态转移的情况就标记,直到递归到dp[0][]或者dp[][0]结束。
参考:https://blog.csdn.net/Ramay7/article/details/52003368
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int MAXDP = 1e3 ;const int MAXS = 1e7 ;int dp[MAXDP][MAXDP];char s1[MAXS], s2[MAXS];int pre[MAXDP][MAXDP]; int vis[MAXDP]; int len_s1, len_s2; void dfs (int len1, int len2) { if (len1 == 0 || len2 == 0 ) return ; if (pre[len1][len2] == 1 ) { vis[len1] = 1 ; dfs (len1 - 1 , len2 - 1 ); } else if (pre[len1][len2] == 2 ) dfs (len1, len2 - 1 ); else dfs (len1 - 1 , len2); } void LCS (char * s1, char * s2) { len_s1 = strlen (s1) - 1 ; len_s2 = strlen (s2) - 1 ; for (int i = 0 ; i <= len_s1; i++) for (int j = 0 ; j <= len_s2; j++) { if (i == 0 || j == 0 ) dp[i][j] = 0 ; else if (s1[i] == s2[j]) { dp[i][j] = dp[i - 1 ][j - 1 ] + 1 ; pre[i][j] = 1 ; } else { if (dp[i][j - 1 ] > dp[i - 1 ][j]) { dp[i][j] = dp[i][j - 1 ]; pre[i][j] = 2 ; } else { dp[i][j] = dp[i - 1 ][j]; pre[i][j] = 3 ; } } } } void print () { int st = 1 ; for (int i = 1 ; i <= len_s1; ++i) { if (vis[i] == 1 ) { for (int j = st; j <= len_s2; ++j) { if (s1[i] == s2[j]) { st = j + 1 ; break ; } cout << s2[j]; } } cout << s1[i]; } for (int j = st; j <= len_s2; ++j) cout << s2[j]; cout << endl; } int main () { s1[0 ] = ' ' , s2[0 ] = ' ' ; ios::sync_with_stdio (false ); while (cin >> s1 +1 >> s2 + 1 ) { memset (dp, 0 , sizeof (dp)); memset (pre, 0 , sizeof (pre)); memset (vis, 0 , sizeof (vis)); LCS (s1, s2); dfs (len_s1, len_s2); print (); } return 0 ; }
题意
给定两个基因字符串,用A,C,G,T表示其组成成分。若两个基因的长度不一样,可以通过在两个串中分别添加空格使其长度一致。当其长度一样后,分别计算对应位置上的两个字母的分数,并将所有的分数相加便得到两个串的相似度分数。求,两个基因串的最高分数。
1 设dp(i,j)为第一个序列(s1)的前i个数和第二个序列(s2)的前j个数的相似度的最大值。当s1[i-1]==s2[j-1]时,由题目给出的表显然可以得出dp(i,j)=dp(i-1,j-1)+score[s1[i-1]][s2[j-1]];score数组为题目中给出的那个表格。当s1[i-1]!=s2[j-1]时,由普通的LCS显然有dp(i,j)=max(d(i-1,j)+score[s1[i-1]]['-'],dp(i,j-1)+score['-'][],d(i-1,j-1)+score[s1[i-1]][s2[j-1]])。于是,两个for就解决问题了。注意初始化数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> #include <algorithm> using namespace std;const int LEN = 1e3 ;int dp[LEN][LEN];int score[LEN][LEN];char c1[LEN];char c2[LEN];int len1, len2;void init () { score['A' ]['A' ]=score['C' ]['C' ]=score['G' ]['G' ]=score['T' ]['T' ]=5 ; score['A' ]['C' ]=score['C' ]['A' ]=score['A' ]['T' ]=score['T' ]['A' ]=score['T' ][' ' ]=score[' ' ]['T' ]=-1 ; score['A' ]['G' ]=score['G' ]['A' ]=score['C' ]['T' ]=score['T' ]['C' ]=score['G' ]['T' ]=score['T' ]['G' ]=score['G' ][' ' ]=score[' ' ]['G' ]=-2 ; score['A' ][' ' ]=score[' ' ]['A' ]=score['G' ]['C' ]=score['C' ]['G' ]=-3 ; score['C' ][' ' ]=score[' ' ]['C' ]=-4 ; dp[0 ][0 ] = 0 ; for (int i = 0 ; i < len1; i++) dp[0 ][i + 1 ] = dp[0 ][i] + score[c1[i]][' ' ]; for (int i = 0 ; i < len2; i++) dp[i + 1 ][0 ] = dp[i][0 ] + score[c2[i]][' ' ]; } int main () { ios::sync_with_stdio (false ); int n; cin >> n; while (n--) { cin >> len1 >> c1; cin >> len2 >> c2; init (); for (int i = 1 ; i <= len1; i++) for (int j = 1 ; j <= len2; j++) dp[j][i] = max (max (dp[j - 1 ][i - 1 ] + score[c2[j - 1 ]][c1[i - 1 ]], dp[j - 1 ][i] + score[c2[j - 1 ]][' ' ]), dp[j][i - 1 ] + score[c1[i - 1 ]][' ' ]); cout << dp[len2][len1] << endl; } return 0 ; }
最长公共子串(连续) 和LCS区别是区别就是因为是连续的,如果两个元素不等,那么就要=0了而不能用之前一个状态的最大元素
区间DP 区间dp,顾名思义就是在一段区间上进行动态规划。对于每段区间,他们的最优值都是由几段更小区间的最优值得到,是分治思想的一种应用,将一个区间问题不断划分为更小的区间直至一个元素组成的区间,枚举他们的组合 ,求合并后的最优值。
算法结构
设F[i,j](1<=i<=j<=n)表示区间[i,j]内的数字相加的最小代价 每次用变量k(i<=k<=j-1)将区间分为[i,k]和[k+1,j]两段
For l:=2 to n do // 枚举区间长度 for i:=1 to n do // 枚举区间的左端点 begin j:=i+l-1; // 计算区间的右端点,因为区间长度和左端点循环嵌套枚举,保证了[i,j]内的所有子区间都被枚举到 if j>n then break; // 保证了下标不越界 for k:= i to j-1 do // 状态转移,去推出 f[i,j] f[i , j]= max{f[ i,k]+ f[k+1,j]+ w[i,j] } end;
作者:Steven1997 链接:https://www.jianshu.com/p/9c6401ea2f9b 來源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。1 2 3 4 5 6 7 ## [poj 1651 Multiplication Puzzle (区间dp)](http://poj.org/problem?id=1651) **题意** ```c++ 给出一个数组a,可以将其中除了头尾两个数之外的任何一个数字a[i]取出数列,需要的花费为 a[i-1] * a[i] * a[i+1],问如果需要把这个数列除了头尾之外的数字都取完,怎样的取出顺序是花费最小的,输出这个最小花费
如果贪心每次取最小的话是错的,看下面一组测试
思路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 首先我们可以考虑最小的一个可以操作的区间为3 个数字,是可以直接算出答案的。 对于长度小于3 的区间,我们可以直接得到 dp[i][i] = 0 ; 对于长度大于3 的区间,我们可以在其中找到一个中点,例如区间 i ~ j 取中点k 即分为区间 i~k 和 k+1 ~j, 对于区间 i~k 我们有答案 dp[i][k] 对于区间 k+1 ~j 我们有答案 dp[k+1 ][j] ,而在这两个区间运算完之后 会剩下的是 a[i],a[k+1 ],a[j+1 ],所以为了消除k我们会有花费 a[i] * a[k+1 ] * a[j+1 ],即转移方程 : dp[i][j]= min (dp[i][k] + dp[k + 1 ][j] + arr[i] * arr[k + 1 ] * arr[j + 1 ]); 先计算ans[i][i + 1 ] ( 1 <= i < N – 1 ),ans[i][i + 2 ] ( 1 <= i < N – 2 ),…… 即计算ans[i][i + j] ( 1 <= j < N – 1 , 1 <= i < N – j ) 那么可以这样写 for (int j = 1 ; j <= n - 1 ; j++) for (int i = 1 ; i <= n - j; i++) { dp[i][i + j] = INF; for (int k = i; k < i + j; k++) { dp[i][i + j]= min (dp[i][k] + dp[k + 1 ][i + j] + arr[i] * arr[k + 1 ] * arr[i + j + 1 ], dp[i][i + j]); } }
参考:
http://www.voidcn.com/article/p-xcolbvjf-bke.html
https://111qqz.com/2016/07/poj-1651/
http://www.acmsearch.com/article/show/16953
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <algorithm> using namespace std;const int INF = 1e10 ;const int MAXN = 1E3 ;int dp[MAXN][MAXN];int arr[MAXN];int main () { ios::sync_with_stdio (false ); int n; cin >> n; for (int i = 1 ; i <= n; i++) { cin >> arr[i]; dp[i][i] = 0 ; } for (int j = 1 ; j <= n - 1 ; j++) for (int i = 1 ; i <= n - j; i++) { dp[i][i + j] = INF; for (int k = i; k < i + j; k++) { dp[i][i + j]= min (dp[i][k] + dp[k + 1 ][i + j] + arr[i] * arr[k + 1 ] * arr[i + j + 1 ], dp[i][i + j]); } } cout<<dp[1 ][n - 1 ]<<endl; return 0 ; }
题解:
http://www.cnblogs.com/grandyang/p/8319913.html
刷题找工作—-leetcode 664 —bili
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : int strangePrinter (string s) { int n = s.size (); vector<vector<int >> dp (n, vector<int >(n, 0 )); for (int i = n - 1 ; i >= 0 ; --i) { for (int j = i; j < n; ++j) { dp[i][j] = (i == j) ? 1 : (1 + dp[i + 1 ][j]); for (int k = i + 1 ; k <= j; ++k) { if (s[k] == s[i]) dp[i][j] = min (dp[i][j], dp[i + 1 ][k - 1 ] + dp[k][j]); } } } return (n == 0 ) ? 0 : dp[0 ][n - 1 ]; } };
题解:
https://blog.csdn.net/magicbean2/article/details/79893634
刷题找工作—-leetcode 813 —bili
递推版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public : double largestSumOfAverages (vector<int >& A, int K) { const int n = A.size (); m_ = vector<vector<double >>(K + 1 , vector<double >(n + 1 , 0.0 )); sums_ = vector<double >(n + 1 , 0.0 ); for (int i = 1 ; i <= n; i++) sums_[i] = sums_[i - 1 ] + A[i - 1 ]; return LSA (A, n, K); } private : vector<vector<double >> m_; vector<double > sums_; double LSA (const vector<int >& A, int n, int k) { if (m_[k][n] > 0 ) return m_[k][n]; if (k == 1 ) return sums_[n] / n; for (int i = k - 1 ; i < n; i++) m_[k][n] = max (m_[k][n], LSA (A, i, k - 1 ) + (sums_[n] - sums_[i]) / (n - i)); return m_[k][n]; } };
DP版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public : double largestSumOfAverages (vector<int >& A, int K) { const int n = A.size (); vector<vector<double >> dp (K + 1 , vector<double >(n + 1 , 0.0 )); vector<double >sums (n + 1 , 0.0 ); for (int i = 1 ; i <= n; i++) { sums[i] = sums[i - 1 ] + A[i - 1 ]; dp[1 ][i] = sums[i] / i; } for (int k = 2 ; k <= K; k++) for (int i = k; i <= n; i++) for (int j = k - 1 ; j < i; j++) { dp[k][i] = max (dp[k][i], dp[k - 1 ][j] + (sums[i] - sums[j]) / (i - j)); } return dp[K][n]; } };
因为dp[k]只与dp[k - 1]有关。所以可以降维
降维版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public : double largestSumOfAverages (vector<int >& A, int K) { const int n = A.size (); vector<double > dp (n + 1 , 0.0 ) ; vector<double > sums (n + 1 , 0.0 ) ; for (int i = 1 ; i <= n; i++) { sums[i] = sums[i - 1 ] + A[i - 1 ]; dp[i] = sums[i] / i; } for (int k = 2 ; k <= K; k++) { vector<double > tmp (n + 1 , 0.0 ) ; for (int i = k; i <= n; i++) for (int j = k - 1 ; j < i; j++) { tmp[i] = max (tmp[i], dp[j] + (sums[i] - sums[j]) / (i - j)); } dp.swap (tmp); } return dp[n]; } };
题意
有n个村庄现在要建立m个邮局,问怎么建邮局才能使得村庄到最近的邮局距离和最小。输出距离和即可。
思路
1 一般的区间dp是从小区间到大区间,而此题在此之外还有一个限制是m个邮局。对于此类问题可以直接建立dp的时候加上限制条件:dp[i][j]=min (dp[k][j−1 ]+one[k+1 ][i]) 定义dp含义为前i个村庄建立了j个邮局的最小距离和,那么在建立第j的时候可以枚举之前已经求出的区间,从j-1 个邮局的前提下加上现在的1 个邮局,one[i][j] 的含义是在区间i到j 的范围之中建立一个邮局,其实也就是中位数的位置,然后递推找出最小的值即可。
参考:
https://blog.csdn.net/since_natural_ran/article/details/77629901
未用优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> #include <algorithm> #include <cstring> using namespace std;const int len = 1e3 ;int dp[len][len]; int onePost[len][len] = {0 }; int arr[len];int numV, numP;void init () { for (int i = 1 ; i <= numV; i++) for (int j = i; j <= numV; j++) onePost[i][j] = onePost[i][j - 1 ] + arr[j] - arr[(i + j) / 2 ]; memset (dp, 0x3f , sizeof (dp)); for (int i = 0 ; i <= numP; ++i) dp[i][i] = 0 ; } int main () { ios::sync_with_stdio (false ); cin >> numV >> numP; for (int i = 1 ; i <= numV; i++) cin >> arr[i]; init (); for (int i = 1 ; i <= numV; i++) for (int j = 1 ; j <= numP; j++) for (int k = j - 1 ; k < i; k++) dp[i][j] = min (dp[i][j], dp[k][j - 1 ] + onePost[k + 1 ][i]); cout << dp[numV][numP]; return 0 ; }
优化
参考:
https://blog.csdn.net/since_natural_ran/article/details/77629901
https://blog.csdn.net/u012139398/article/details/43956985
http://www.cnblogs.com/vongang/archive/2013/01/21/2869315.html
四边形优化公式:
1 ss[i, j-1 ] <= ss[i,j] <= ss[i + 1 , j]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <iostream> #include <algorithm> #include <cstring> using namespace std;int numVillages, numPost;const int MAXN = 1E3 ;int dp[MAXN][MAXN];int onePost[MAXN][MAXN];int arr[MAXN];int ss[MAXN][MAXN]; void init () { memset (dp, 0x3f , sizeof (dp)); memset (ss, 0 , sizeof (ss)); for (int i = 0 ; i<= numPost; i++) dp[i][i] = 0 ; for (int i = 1 ; i <= numVillages; i++) for (int j = i; j <= numVillages; j++) onePost[i][j] = onePost[i][j - 1 ] + arr[j] - arr[(i + j) / 2 ]; } int main () { ios::sync_with_stdio (false ); cin >> numVillages >> numPost; for (int i = 1 ; i <= numVillages; i++) cin >> arr[i]; init (); for (int i = 1 ; i <= numVillages; i++) { for (int j = 1 ; j <= numPost; j++) { ss[i + 1 ][j] = i + 1 ; for (int k = ss[i][j - 1 ]; k <= ss[i + 1 ][j]; k++) { if (dp[i][j] > dp[k][j - 1 ] + onePost[k + 1 ][i]) { dp[i][j] = dp[k][j - 1 ] + onePost[k + 1 ][i]; ss[i][j] = k; } } } } cout << dp[numVillages][numPost] << endl; return 0 ; }
压缩二维最大子序列和 题意:大致是求二维数组的最大矩阵和。
参考:
https://www.cnblogs.com/BlackStorm/p/4922207.html
https://blog.csdn.net/linraise/article/details/16349527
https://blog.csdn.net/hitwhylz/article/details/11848439
二维压缩成一维,然后按照求最大序列和的办法去求最大值就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <iostream> #include <climits> #include <cstring> #include <algorithm> using namespace std;const int MAXN = 200 ;int arr[MAXN][MAXN];int cntRow[MAXN];int n;int maxSubArr () { int dp[MAXN] = {0 }; int MAX = INT_MIN; for (int i = 1 ; i <= n; ++i) { dp[i] = max (dp[i - 1 ] + cntRow[i], cntRow[i]); if (MAX < dp[i]) MAX = dp[i]; } return MAX; } int solve () { int ans = INT_MIN; for (int i = 1 ; i <= n; ++i) { memset (cntRow, 0 , sizeof (cntRow)); for (int j = i; j <= n; ++j) { for (int k = 0 ; k <= n; ++k) cntRow[k] += arr[j][k]; int tmp = maxSubArr (); if (tmp > ans) ans = tmp; } } return ans; } int main () { while (cin >> n) { for (int i = 1 ; i <= n; ++i) { for (int j = 1 ; j <= n; ++j) { cin >> arr[i][j]; } } cout << solve () << endl; } return 0 ; }
杂项二维dp转移 题意:给你三个字符串,问你能否由前两个合成第三个,且要求不改变前两个字符串的顺序。题目保证第三个字符串的长度为前两个字符串的长度之和。
参考:
https://www.cnblogs.com/huashanqingzhu/p/7348218.html
1 2 3 4 5 最优子结构分析:如果A、B可以组成C,那么,C最后一个字母,必定是 A 或 B 的最后一个字母组成。 C去除除最后一位,就变成是否可以求出 A-1 和B 或者 A与B-1 与 是否可以构成 C-1 。。。 状态转移方程: 用f[i][j] 表示 A前 i 位 和B 前j 位是否可以组成 C的前i+j位 dp[i][j]= (dp[i-1 ][j]&&(a[i]==c[i+j]))||(dp[i][j-1 ]&&(b[j]==c[i+j]))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <iostream> #include <cstring> using namespace std;const int MAXN = 1E3 ;char a[MAXN], b[MAXN], c[MAXN];int dp[MAXN][MAXN];int main () { ios::sync_with_stdio (false ); int n; cin >> n; for (int k = 1 ; k <= n; k++) { memset (dp, 0 , sizeof (dp)); a[0 ] = b[0 ] = c[0 ] = ' ' ; cin >> a + 1 >> b + 1 >> c + 1 ; int lenA = strlen (a); int lenB = strlen (b); int lenC = strlen (c); for (int i = 0 ; i < lenA; i++) { if (a[i] == c[i]) dp[i][0 ] = 1 ; } for (int i = 0 ; i < lenB; i++) { if (b[i] == c[i]) dp[0 ][i] = 1 ; } for (int i = 1 ; i <= lenA; i++) for (int j = 1 ; j <= lenB; j++) { dp[i][j] = ( (dp[i - 1 ][j] && a[i] == c[i + j]) || (dp[i][j - 1 ] && b[j] == c[i + j]) ); } cout << "Data set " << k << ": " ; if (dp[lenA][lenB]) cout << "yes" << endl; else cout << "no" << endl; } return 0 ; }
题意: 给出两个字符串x 与 y,其中x的长度为n,y的长度为m,并且m>=n.然后y可以经过删除一个字母,添加一个字母,转换一个字母,三种操作得到x.问最少可以经过多少次操作
参考:
https://blog.csdn.net/u013480600/article/details/40780781
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <algorithm> using namespace std;const int LEN = 1e3 + 10 ;int dp[LEN][LEN];char s1[LEN], s2[LEN];int n1, n2;int main () { ios::sync_with_stdio (false ); while (cin >> n1 >> s1) { cin >> n2 >> s2; for (int i = 0 ; i <= n1; i++) dp[i][0 ] = i; for (int i = 0 ; i <= n2; i++) dp[0 ][i] = i; for (int i = 1 ; i <= n1; i++) for (int j = 1 ; j <= n2; j++) dp[i][j]=min (dp[i-1 ][j-1 ]+(s1[i-1 ]==s2[j-1 ]?0 :1 ), min (dp[i-1 ][j]+1 ,dp[i][j-1 ]+1 )); cout << dp[n1][n2] << endl; } return 0 ; }