疯狂 dp(一) 线性 dp
来源:互联网 发布:php判断会员登录 编辑:程序博客网 时间:2024/05/21 18:54
动态规划是为了求解一种包含大量重叠子问题的最优化问题的方法。基本思想是,将原问题分解为若干相似的子问题,在求解的过程中通过子问题的解求出原问题的解。听起来和分治法很相似,但是,分治法只是不断地将问题分解成小问题求解,而动规之所以优秀是它会进行类似于记忆化搜索的过程,在求解的过程中把每一个子问题的解保存下来(不管后面会不会用到),然后在求解更大的问题时,从这些子问题的解里面寻求当前问题的最优解。所以,从这里也可以看出动规解法的限定条件是,该问题有一个最优子结构,就是由它分解出的子问题的解也是最优的。
这样看起来是很抽象的,我们接下来一个个模型看下去吧。感觉对 dp 不太好分类,个人就做简单的整理,代码不对或是分类有问题请大家指出,谢谢!
首先是线性动规。
单调递增最长子序列
这个问题的基本模型是,给一个序列,要求它的最长递增子序列,子序列的值是递增的,它的坐标在原序列中也是递增的,但不要求连续。
如,给一串数字, 0 2 3 4 3 4 6 9 13 24 11 26,那么它的单调递增最长子序列就是 0 2 3 4 6 9 13 24 26
给一串字母,a f r e f j l m o d, 单调递增最长子序列就是 a e f j l m o
下面是求一个字母序列的单调递增子序列
原题:http://acm.nyist.net/JudgeOnline/problem.php?pid=17
解1:
输入字符串为 str, 用一个数组 dp 记录问题的解, dp[i] 表示以 str[i] 结束的单调递增最长子序列的长度。
状态转移方程: dp[i] = max(1, dp[j] + 1) , str[j] < str[i]
复杂度为 O(n^2)
代码:
#include <iostream>#include <string>#include <cstring>using namespace std;#define max(a,b) (a)>(b)?(a):(b)int main(){int dp[10001],ans;int T;cin>>T;while(T--){ans = 1;string str;cin>>str;int n = str.size();for(int i = 0;i < n;++i){dp[i] = 1;for(int j = 0;j < i;++j){if(str[i] > str[j]){dp[i] = max(dp[i],dp[j] + 1);if(dp[i] > ans) ans = dp[i];}}}cout<<ans<<endl;}return 0;}
解二:
数组 dp 存不是答案子序列,但它和子序列长度是一样的,因为它的不断更新,所以总能确保答案正确,多看几遍。复杂度大概只有 O(100*N),因为最长单调递增子序列的长度是不可能超过127的。
#include<iostream>#include<string>#include<cstring>using namespace std;//求最长单调递增子序列长度int length(string str){int dp[128], ans = 1;;memset(dp,0,sizeof(dp));int n = str.size();dp[0] = str[0]; //初始化for(int i = 0;i < n;++i){for(int j = ans - 1;j >= 0; --j){if(j == 0 && dp[0] > str[i]) dp[0] = str[i];if(dp[j] < str[i]){dp[j+1] = str[i];if(j == ans - 1) ans++;break;}}}return ans;}int main(){int T;string str;cin>>T;while(T--){cin>>str;cout<<length(str)<<endl;}return 0;}
单调递增最长子序列应用:
拦截导弹:
原题:http://acm.nyist.net/JudgeOnline/problem.php?pid=79
题意:就是给一个序列然后求它的单调递减最长子序列的长度。
思路:按照最长单调递增子序列的思路,只要把递增的比较改为递减的比较就好,length1 函数为上面解法一的修改解法, length2 为上面解法二的修改解法,而且运用二分查找存放位置。
代码:
/* * http://acm.nyist.net/JudgeOnline/problem.php?pid=79 * * 求最长单调递减子序列长度 */#include<iostream>#include<cstring>#include <cstdio>using namespace std;#define MAXN 20 + 1#define max(a,b) (a)>(b)?(a):(b) int num[MAXN];int dp[MAXN];//直接 dp ,O(n^2)int length1(int *num,int n,int *dp){int ans = 1;memset(dp,0,sizeof(dp));for(int i = 0;i < n;++i){dp[i] = 1;for(int j = 0;j < i;++j){if(num[j] > num[i]){dp[i] = max(dp[i],dp[j] + 1);ans = max(ans,dp[i]);}}}return ans;}//求最长单调递减子序列长度,运用二分int length2(int *num, int n, int *dp){int ans = 1;;memset(dp,0,sizeof(dp));dp[0] = num[0]; //初始化for(int i = 0;i < n;++i){//二分查找 num[i] 应放的位置int st = 0,ed = ans - 1;while(st <= ed){int mid = (st + ed)/2;if(dp[mid] == num[i]) break;if(dp[mid] > num[i]){st = mid + 1;}else{ed = mid - 1;}}// st > ed 的时候才要将 num[i] 放进去,用纸演算一下,要放在 dp[st] 的位置if(st > ed){dp[st] = num[i];if(st == ans) ans++;}}return ans;}int main(){int N,M;scanf("%d",&N);while(N--){scanf("%d",&M);for(int i = 0;i < M;++i)scanf("%d",&num[i]);cout<<length1(num, M, dp)<<endl;}return 0;}
合唱队形:
原题: http://www.rqnoj.cn/problem/26
题意:给 n 个整数,求一个子序列,有一个中间数 i, 使得 i 的左边递增,右边递减,要这个子序列最长。输出 n - length, length 为该子序列的长度。
思路:这还是单调递增最长子序列的问题,只是多了一步,要一个左边递增右边递减加起来最长的序列,数据较小,所以我们可以枚举。对队列里的每个数 i ,计算以它为中心的最长递增和最长递减,长度加起来和答案比较,更新 ans. 复杂度为 O(n^3) 。
代码:
/* * http://www.rqnoj.cn/problem/26*/#include <iostream>#include <cstring>#include <cstdio>#include <string>using namespace std;#define maxn 103int n,p[maxn],dp[maxn];int ans;int solve(){for(int i = 1;i <= n;++i){for(int j = 1;j <= n;++j){dp[j] = 1;for(int k = 1;k < j;++k){if(p[k] < p[j]) dp[j] = max(dp[j],dp[k]+1);}} int l = dp[i];for(int j = n;j >= i;--j){dp[j] = 1;for(int k = n;k > j;--k){if(p[k] < p[j]) dp[j] = max(dp[j],dp[k]+1);}}int r = dp[i];ans = max(ans,l+r-1);}return n-ans;}int main(){while(scanf("%d",&n)!=EOF){for(int i = 1;i <= n;++i)scanf("%d",&p[i]);printf("%d\n",solve());}return 0;}
子串和
这是线性 dp 的另一种类型。给出 n 个整数,求一个连续的子串,使得这个子串的和在所有子串中最大。
原题:http://acm.nyist.net/JudgeOnline/problem.php?pid=44
思路:这道题的空间复杂度可以降到 O(1),不需要把所有的数存起来,只要一边输入一边判断就好。我们用 last 表示到前一位的最大子串和,初始化是 0, 那么以某个数结尾的最大子串和的最坏情况就是它本身,如果 last >0,到当前数的最大子串和肯定是当前数加上 last,然后动态更新 ans 就好。
代码:
/* * http://acm.nyist.net/JudgeOnline/problem.php?pid=44 */#include <iostream>#include <cstdio>using namespace std;#define INF -99999999#define max(a,b) (a)>(b)?(a):(b)int main(){int T,n,last,cur;scanf("%d",&T);while(T--){int sum = INF;last = 0;scanf("%d",&n);for(int i = 0;i < n;++i){scanf("%d",&cur);//这是关键一步,如果前面的子串和 >0,那么这一步的最大子串和为这一个数加上前一步的最大子串和if(last > 0) cur += last;sum = max(sum,cur);last = cur;}printf("%d\n",sum);}return 0;}
01串
原题:http://acm.nyist.net/JudgeOnline/problem.php?pid=252
题意:长度为 n 的所有 0 1 串中,不含 11 的有多少个。
思路:dp[i] 表示长度为 i 的 0 1串不含 11 的有多少个。初始条件是 dp[1] = 2 dp[3] = 3
算dp[i] 的时候,如果 i 位为 0 ,那么前 i-1 位任取,所以 dp[i] += dp[i-1]
如果 i 位为 1,那么 i-1 位只能为 0,前 i-2 位任取,所以 dp[i] += dp[i-2]
状态方程: dp[i] = dp[i-1] + dp[i-2]
代码:
/* * http://acm.nyist.net/JudgeOnline/problem.php?pid=252dp[i]表示第i为不含11的01串个数第i位为0,则前 i-1 位可任取第i位为1,则 i-1 位必为0,前 i-2 位任取所以 dp[i] = dp[i-1] + dp[i-2];*/#include<iostream>#include<cmath>using namespace std;#define MAXN 42int main(){int dp[MAXN],n;dp[2] = 3;dp[3] = 5;for(int i = 4;i <= 40;++i)dp[i] = dp[i-1] + dp[i-2];cin>>n;while(n--){int k;cin>>k;cout<<dp[k]<<endl;}return 0;}
原题:http://soj.me/1001
题意:使用一种加密方法,把 A-Z 映射为 1 - 26,那么根据一个加密后的序列,可能有多种解密方法,比如给 124,你可以理解成 1、2、4,也可以看成 12、4,或者1 、24,所以要求求出共有多少中可能。
思路:用 dp[i] 表示到第 i 位有多少种解密方法,则可以看到 dp[i] 和 dp[i-1] dp[i-2] 的关系。如果第 i 个数不是 0 ,那么这个数可以作为单独一个字母进行解码,前面 i-1 位任取,所以 dp[i] += dp[i-1] 。如果第 i-1 位是 1 或者 i-1 位是2,i 位是小于 7 的数,那么它们可以翻译为一个字母解码,前 i-2 位任取,所以 dp[i] += dp[i-2]。
代码:
/* *http://soj.me/1001 */#include <iostream>#include <string>#include <cstring>using namespace std;int main(){string str;int ans[10000];int n;while(cin>>str&&str!="0"){memset(ans,0,sizeof(ans));ans[0] = 1;ans[1] = 1;n = str.size();for(int i=1;i<n;++i){if(str[i]!='0') ans[i+1] += ans[i];if(str[i-1]=='1'||(str[i-1]=='2'&&str[i]<'7')) ans[i+1] += ans[i-1];}cout<<ans[n]<<endl;}return 0;}
- 疯狂 dp(一) 线性 dp
- 线性dp<一>
- 疯狂的bLue(dp)
- hdu1421(线性DP)
- 滑雪(线性dp)
- 【简单的线性DP】SDUT 3924 疯狂的bLue
- 线性DP
- 线性dp
- 线性dp
- 线性dp
- 合唱队形(一维线性dp递推)
- DP专辑之线性DP
- lightoj 1047 - Neighbor House DP(简单线性DP)
- DP(一)整理
- RQNOJ-252-公司聚会(线性dp)
- POJ 2355 Railway tickets (线性dp)
- ACdream Kill The Monster(线性dp)
- POJ 1925 Spiderman(线性dp)
- 1 数据结构基础概念
- Lake Counting
- 微信公众平台开发C#实现
- Introduction to SSE Programming
- c++设计模式之观察者模式
- 疯狂 dp(一) 线性 dp
- 获得PHP环境系统信息
- 采用邻接表存储结构,编写一个判别无向图中任意给定的两个顶点之间是否存在一条长度为k的简单路径的算法。
- CLOSE BY CLIENT STACK TRACE ,Unable to install breakpoint解决
- 小波变换
- 安装OpenCV Ubuntu官方指南
- 设计模式-简单工厂模式
- c函数之【记录函数】
- Java的package和import机制