2020.2.11动态规划/记忆递归总结+例题(1191棋盘分割 1661帮助jimmy 2760数字三角形 4152最佳加法表达式 2757最长上升子序列 1458Common Subsequen)
【思路总结】
1.什么时候使用动态规划(记忆递归):
当题目所求解为一个最优化问题(最小,最大等)问题的时候,并且问题可以进一步分解为求解每个子状态的最优,就应该努力寻找所求状态到子状态的递推关系,进行动态规划
2.关于动态规划和记忆型递归的关系:
一般来说,对于一个我们可以找到递推式的题目而言,既可以使用记忆型暴力递归(写法和死路上简单),也可以使用动态规划(减少栈的溢出问题,但是思维上比较困难。好处是可以进行滚动数组等形式的空间优化和各种奇技淫巧的时间优化)。
《从递归到递推》https://blog.csdn.net/qq_43326818/article/details/99329784
1.首先写出题目的暴力递归版本
2.观察在递归版本中变量是哪几个,然后根据变量建立动态规化的数组,明确动态规化方程的含义是什么
3.根据动态规化数组填充的方式决定计算的顺序以及动态规化初始条件和动态规化方程。并且要明确我们要求的是动态规化方程的哪一项
但是从openjudge上面来说,单纯把写法从递归写成动规并不是非常明显的降低时间复杂度(尤其是数据量小的时候),动规只是方便了后续进行算法上的空间时间优化。
3.记忆数组和动态规划的维数及“关键字”如何寻找:
一般每个状态需要几个参数确定,那么数组就是几维的,我们可以通过精简参数来节省空间和时间复杂度。类似于物理中找一个状态的“自由度”。
而关键字只要将该状态的参数写入对应自由度的位置即可。
【例题】
1191棋盘分割
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<cmath> using namespace std; int place[8][8],sumx[8][8];//第ij格子中储存从00到ij这些个格子上数字的合,一个简化计算的方法 long long mark[15][8][8][8][8]; long long cut(int n,int x_beg,int y_beg,int x_tar,int y_tar){ int max_n=0; if(x_beg==0&&y_beg==0) max_n=x_tar*y_tar; else if(x_beg==0) max_n=x_tar*y_tar-x_tar*(y_beg-1); else if(y_beg==0) max_n=x_tar*y_tar-y_tar*(x_beg-1); else max_n=x_tar*y_tar+(x_beg-1)*(y_beg-1)-x_tar*(y_beg-1)-y_tar*(x_beg-1); if(mark[n][x_beg][y_beg][x_tar][y_tar]!=-1) return mark[n][x_beg][y_beg][x_tar][y_tar]; else if(n==1){ if(x_beg==0&&y_beg==0){ mark[1][x_beg][y_beg][x_tar][y_tar]=sumx[x_tar][y_tar]*sumx[x_tar][y_tar]; return mark[1][x_beg][y_beg][x_tar][y_tar]; } else if(x_beg==0){ int temp=sumx[x_tar][y_tar]-sumx[x_tar][y_beg-1]; mark[1][x_beg][y_beg][x_tar][y_tar]=temp*temp; return mark[1][x_beg][y_beg][x_tar][y_tar]; } else if(y_beg==0){ int temp=sumx[x_tar][y_tar]-sumx[x_beg-1][y_tar]; mark[1][x_beg][y_beg][x_tar][y_tar]=temp*temp; return mark[1][x_beg][y_beg][x_tar][y_tar]; } else{ int temp=sumx[x_tar][y_tar]+sumx[x_beg-1][y_beg-1]-sumx[x_tar][y_beg-1]-sumx[x_beg-1][y_tar]; mark[1][x_beg][y_beg][x_tar][y_tar]=temp*temp; return mark[1][x_beg][y_beg][x_tar][y_tar]; } } else if(max_n<n){ mark[n][x_beg][y_beg][x_tar][y_tar]=6400*6400+100; return mark[n][x_beg][y_beg][x_tar][y_tar]; } else{ long long x3,min_x3=6400*6400+100; for(int k=x_beg;k<x_tar;++k){ x3=cut(1,x_beg,y_beg,k,y_tar)+cut(n-1,k+1,y_beg,x_tar,y_tar); if(x3<min_x3) min_x3=x3; x3=cut(n-1,x_beg,y_beg,k,y_tar)+cut(1,k+1,y_beg,x_tar,y_tar); if(x3<min_x3) min_x3=x3; } for(int k=y_beg;k<y_tar;++k){ x3=cut(1,x_beg,y_beg,x_tar,k)+cut(n-1,x_beg,k+1,x_tar,y_tar); if(x3<min_x3) min_x3=x3; x3=cut(n-1,x_beg,y_beg,x_tar,k)+cut(1,x_beg,k+1,x_tar,y_tar); if(x3<min_x3) min_x3=x3; } mark[n][x_beg][y_beg][x_tar][y_tar]=min_x3; return min_x3; } } int main(){ int n; cin>>n; for(int i=0;i<8;++i){ for(int j=0;j<8;++j) cin>>place[i][j]; } memset(mark,-1,sizeof(mark));//memset可以置为-1 memset(sumx,0,sizeof(sumx)); for(int i=0;i<8;++i){ for(int j=0;j<8;++j){ if(i==0&&j==0) sumx[i][j]=place[i][j]; else if(j==0) sumx[i][j]=sumx[i-1][j]+place[i][j]; else if(i==0) sumx[i][j]=sumx[i][j-1]+place[i][j]; else sumx[i][j]=place[i][j]+sumx[i-1][j]+sumx[i][j-1]-sumx[i-1][j-1]; } } double x_=double(sumx[7][7])/n;//x平均值 long long x2,min_x2=6400*6400+100; for(int i=0;i<=6;++i){ x2=cut(1,0,0,i,7)+cut(n-1,i+1,0,7,7); if(x2<min_x2) min_x2=x2; //cout<<x2<<" "<<i<<"横切,取下"<<endl; x2=cut(n-1,0,0,i,7)+cut(1,i+1,0,7,7); if(x2<min_x2) min_x2=x2; //cout<<x2<<" "<<i<<"横切,取上"<<endl; x2=cut(1,0,0,7,i)+cut(n-1,0,i+1,7,7); if(x2<min_x2) min_x2=x2; //cout<<x2<<" "<<i<<"竖切,取右"<<endl; x2=cut(n-1,0,0,7,i)+cut(1,0,i+1,7,7); if(x2<min_x2) min_x2=x2; //cout<<x2<<" "<<i<<"竖切,取左"<<endl; } double e=sqrt(double(min_x2)/n-x_*x_); printf("%.3lf",e); return 0; }
动规(记忆递归)算法总结:
1.对于存入的数据开辟一个全局数组(方便递归函数中简洁调用),在开辟一个名为mark的记录数组,并使用memset将其初始化为-1(memset设置0或者-1时写法简单)。
2.每次进入递归的时候都要判断记忆数组中是否已经有存储(!=-1),如果有直接返回。否则在每一步有返回值的之后都要更新记录数组
3.针对本题目来说,递推关系显而易见,不做进一步说明。
C++用法总结:
1.memset在cstring库中
2.cmath库中的pow函数不是非常适合用于计算整数的平方,因为他的返回值是浮点数,自动转化为整型的时候会出现24.99999(5^2)转化为24的情况,出现后续难以察觉的错误。但是计算开方,非整数的平方时则非常方便。
详见:https://blog.csdn.net/weixin_43583397/article/details/84667561
3.关于类型的转换:double型的数据如果赋值为两个int相除也是整数。一定要把另一个强制转化为double
eg:程序中的97和117行位置,就因为没有加double出现过精密度丢失。
1661 帮助jimmy
A:记忆递归版本
//vector 用法 #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<vector> #include<algorithm> using namespace std; class table{ public: int s[2],h; bool operator<(table& tb){ if(h==tb.h) return s[0]<tb.s[0]; return h>tb.h; } }; int MAX; int mark[1010][2];//记录i号板分别左端下落和右端下去的最小时间 int jump(vector<table> tables,int x,int n,int d){ if(mark[n][d]!=-1) return (d==0)?(x-tables[n].s[0]+mark[n][d]):(tables[n].s[1]-x+mark[n][d]); else{ int next=-1; if(d==0){//向左走,向右走类似 for(int k=n;k<tables.size();++k){//能不能简化为二分查找,用bsearch优化 if(tables[k].h<tables[n].h-MAX) break; if(tables[k].h<tables[n].h && tables[k].s[0]<=tables[n].s[0] && tables[n].s[0]<=tables[k].s[1]){ next=k; break; } } if(next==-1){//max范围内没有台子了 if(tables[n].h<=MAX){//可以直接安全落地 mark[n][d]=tables[n].h; return x-tables[n].s[0]+tables[n].h; } else{//不能直接安全落地 mark[n][d]=99999; return mark[n][d]; } } else{//max范围内下一个安全台子的序号是next mark[n][d]=(tables[n].h-tables[next].h)+min(jump(tables,tables[n].s[0],next,0),jump(tables,tables[n].s[0],next,1)); return mark[n][d]+x-tables[n].s[0]; } } else {//从右边跳 for(int k=n;k<tables.size();++k){//能不能简化为二分查找? if(tables[k].h<tables[n].h-MAX) break; if(tables[k].h<tables[n].h && tables[k].s[0]<=tables[n].s[1] && tables[n].s[1]<=tables[k].s[1]){ next=k; break; } } if(next==-1){//max范围内没有台子了 if(tables[n].h<=MAX){//可以直接安全落地 mark[n][d]=tables[n].h; return tables[n].s[1]-x+tables[n].h; } else{//不能直接安全落地 mark[n][d]=99999; return mark[n][d]; } } else{//max范围内下一个安全台子的序号是next mark[n][d]=(tables[n].h-tables[next].h)+min(jump(tables,tables[n].s[1],next,0),jump(tables,tables[n].s[1],next,1)); return mark[n][d]+tables[n].s[1]-x; } } } } int main(){ int t; cin>>t; int n,x,y; vector<table> tables; vector<table>::iterator p1,p2; for(int iii=0;iii<t;++iii){ memset(mark,-1,sizeof(mark)); cin>>n>>x>>y>>MAX; table temp; for(int ii=0;ii<n;++ii){ cin>>temp.s[0]>>temp.s[1]>>temp.h; tables.insert(tables.end(),temp); } p1=tables.begin();p2=tables.end(); sort(p1,p2); int start; for(start=0;start<tables.size();++start){ if(tables[start].h<=y && y<=tables[start].h+MAX && tables[start].s[0]<=x && x<=tables[start].s[1]) break; } if(start==tables.size())//得考虑直接跳地上的情况 cout<<y<<endl; else{ int ans=y-tables[start].h; ans+=min(jump(tables,x,start,0),jump(tables,x,start,1));//x初始横坐标,start初始板位置,0初始向左走,1初始向右走 cout<<ans<<endl; } tables.clear(); } return 0; }
B:动态规划版本
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; class table{ public: int s[2],h; table(){ s[0]=0; s[1]=0; h=0; } bool operator<(table& tb){ if(h==tb.h) return s[0]<tb.s[0]; return h>tb.h; } }; int MAX; int mark[1010][2];//记录i号板分别左端下落和右端下去的最小时间 table tables[1010]; int main(){ int t; cin>>t; int n,x,y; for(int iii=0;iii<t;++iii){ memset(mark,-1,sizeof(mark)); cin>>n>>x>>y>>MAX; for(int ii=0;ii<n;++ii){ cin>>tables[ii].s[0]>>tables[ii].s[1]>>tables[ii].h; } sort(tables,tables+n); int start; for(start=0;start<n;++start){ if(tables[start].h<=y && y<=tables[start].h+MAX && tables[start].s[0]<=x && x<=tables[start].s[1]) break; } if(start==n)//得考虑直接跳地上的情况 cout<<y<<endl; else{//如果不会直接跳到地上 for(int kk=n-1;kk>=start;--kk){//遍历kk号台子 int next=-1;//先解决所有从左边下的 for(int k=kk;k<n;++k){ if(tables[k].h<tables[kk].h-MAX) break; if(tables[k].h<tables[kk].h && tables[k].s[0]<=tables[kk].s[0] && tables[kk].s[0]<=tables[k].s[1]){ next=k; break; } } if(next==-1){ if(tables[kk].h<=MAX) mark[kk][0]=tables[kk].h; else mark[kk][0]=99999; } else{//下一个安全台子的序号是next mark[kk][0]=tables[kk].h-tables[next].h+min(tables[kk].s[0]-tables[next].s[0]+mark[next][0],tables[next].s[1]-tables[kk].s[0]+mark[next][1]); } next=-1;//再解决从右边下的 for(int k=kk;k<n;++k){ if(tables[k].h<tables[kk].h-MAX) break; if(tables[k].h<tables[kk].h && tables[k].s[0]<=tables[kk].s[1] && tables[kk].s[1]<=tables[k].s[1]){ next=k; break; } } if(next==-1){ if(tables[kk].h<=MAX) mark[kk][1]=tables[kk].h; else mark[kk][1]=99999; } else{//下一个安全台子的序号是next mark[kk][1]=tables[kk].h-tables[next].h+min(tables[kk].s[1]-tables[next].s[0]+mark[next][0],tables[next].s[1]-tables[kk].s[1]+mark[next][1]); } } int ans=y-tables[start].h; ans+=min(x-tables[start].s[0]+mark[start][0],tables[start].s[1]-x+mark[start][1]);//x初始横坐标,start初始板位置,0初始向左走,1初始向右走 cout<<ans<<endl; } } return 0; }
递推想法:
1.将板子这个类按照从高到低的顺序进行排序(重载小于号或者写一个比较函数)
2.我是用的思路是小鼠每次刚刚落在一个板子上作为一个状态,那么就需要三个参数来确定这个状态:①横坐标x ②板子的编号(写成一个结构体,同时确定了高度,左右端点坐标)③向左走或向右走
但是可以进行这样的优化:实际上虽然递归函数需要传入的参数有上面两个,但是我们可以简化记忆数组,为二维(即两个参数):板子编号&左端/右端。这样数组里记录的是分别从n号板左边和右边落到地上的最短时间,减少可空间复杂度。
3.一些奇怪的边界情况要提前考虑清楚,比如jimmy开始落下到第一个板子的时候也有一段距离;jimmy直接落到地上的情况。这些有可能导致数组越界的问题(0xC0000005错误)
关于0xC0000005错误:https://blog.csdn.net/misayaaaaa/article/details/78818649
C++用法方面:
1.vector
https://blog.csdn.net/civiliziation/article/details/23843429
1.push_back() 在数组的最后添加一个数据
2.pop_back() 去掉数组的最后一个数据
3.at() 得到编号位置的数据
4.begin() 得到数组头的指针
5.end() 得到数组的最后一个单元+1的指针
6.front() 得到数组头的引用
7.back() 得到数组的最后一个单元的引用
8.max_size() 得到vector最大可以是多大
9.capacity() 当前vector分配的大小
10.size() 当前使用数据的大小
11.resize() 改变当前使用数据的大小,如果它比当前使用的大,者填充默认值
12.reserve() 改变当前vecotr所分配空间的大小
13.erase() 删除指针指向的数据项
14.clear() 清空当前的vector
15.rbegin() 将vector反转后的开始指针返回(其实就是原来的end-1)
16.rend() 将vector反转构的结束指针返回(其实就是原来的begin-1)
17.empty() 判断vector是否为空
18.swap() 与另一个vector交换数据
19 insert(p,x) 在迭代器p指向的位置插入元素x
2.但是其实写动规题的时候还是普通的数组好写一点,目前的感觉vector有些鸡肋
2760数字三角形
A.递归写法
#include<cstdlib> #include<cstdio> #include<cstring> #include<iostream> using namespace std; int angle[110][110]; int mark[110][110];//如果是动规可以在这一步进行滚动数组优化 int n; int max_sum(int i,int j){ if (mark[i][j]!=-1) return mark[i][j]; else if(i==n-1){ mark[i][j]=angle[i][j]; return mark[i][j]; } else{ mark[i][j]=max(max_sum(i+1,j),max_sum(i+1,j+1))+angle[i][j]; return mark[i][j]; } } int main(){ cin>>n; memset(angle,0,sizeof(angle)); memset(mark,-1,sizeof(mark)); for(int i=0;i<n;++i){ for(int j=0;j<=i;++j) cin>>angle[i][j]; } cout<<max_sum(0,0)<<endl; return 0; }
B.动规写法
#include<cstdlib> #include<cstdio> #include<cstring> #include<iostream> using namespace std; int angle[110][110]; int mark[2][110];//滚动数组进行优化 int n; int main(){ cin>>n; memset(angle,0,sizeof(angle)); memset(mark,0,sizeof(mark)); for(int i=0;i<n;++i){ for(int j=0;j<=i;++j) cin>>angle[i][j]; } for(int i=0;i<=n-1;++i) mark[0][i]=angle[n-1][i]; //以上输入边界条件,以下进行递归 for(int i=n-2;i>=0;--i){ for(int j=0;j<=i;++j) mark[(n+1-i)%2][j]=max(mark[(n-i)%2][j],mark[(n-i)%2][j+1])+angle[i][j]; } cout<<mark[(n+1)%2][0]<<endl; return 0; }
递推关系及相关优化:
1.递推关系的想法比较显然,每一步选取与左边或者右边相加得到的最大值即可。
2.动态规划中利用了滚动数组进行优化,因为我们发现每一次使用完某一行之后便不会再用了,因此我们只需要开辟一个两行的mark数组即可滚动使用(使用取余数进行滚动)。甚至可以进一步优化,利用存储输入数据的数组的最后一行,这样我们的mark只需要一行就够了。(但是代码的复杂程度会上升,并不实用)
4152最佳加法表达式
A:前置技能:高精度加法 (poj 2981)
#include<cstdio> #include<cstring> #include<iostream> #include<cstdlib> using namespace std; pair<int*,int> PLUS(int* a,int size_a,int* b,int size_b){//a有多少位,b有多少位.返回:数组地址,有多少位 //0的坑 if(size_a==0&&size_b==0){ int * temp=new int [2]; temp[0]=0; return make_pair(temp,1); } int max_size=max(size_a,size_b),min_size=min(size_a,size_b); int *ans; int max_c[max_size+5],min_c[min_size+5]; ans=new int[max_size+10]; memset(ans,0,sizeof(int)*(max_size+10)); memset(max_c,0,sizeof(int)*(max_size+5)); memset(min_c,0,sizeof(int)*(max_size+5)); if(size_a>=size_b){ memcpy(max_c,a,size_a*sizeof(int)); memcpy(min_c,b,size_b*sizeof(int)); } else{ memcpy(min_c,a,size_a*sizeof(int)); memcpy(max_c,b,size_b*sizeof(int)); } for(int i=max_size-1;i>=0;--i){ if(max_size-i>min_size) ans[i+1]+=max_c[i];//留一个进位的 else ans[i+1]+=max_c[i]+min_c[min_size+i-max_size]; } for(int i=max_size;i>=0;--i){ if(int(ans[i])>9){ ans[i-1]+=1; ans[i]-=10; } } int count_=0; for(;count_<=max_size+1;++count_){ if(ans[count_]!=0) break; } return make_pair(ans+count_,max_size+1-count_); } int main(){ int a[200],b[200],size_a=0,size_b=0; while(true){ char x=getchar(); if(size_a==0 && int(x)-48==0) continue; else if(x=='\n') break; else{ a[size_a]=int(x)-48; size_a++; } } while(true){ char x=getchar(); if(size_b==0 && int(x)-48==0) continue; else if(x=='\n') break; else{ b[size_b]=int(x)-48; size_b++; } } auto answer=PLUS(a,size_a,b,size_b); for(int i=0;i<answer.second;++i){ cout<<answer.first[i]; } cout<<endl; }
注:使用内置的pair来存储高精度数。first是数组指针,second数这个数的位数,在本题实现中改为使用加法重载。
B.本题:
#include<cstdio> #include<cstring> #include<iostream> #include<cstdlib> using namespace std; pair<int*,int> operator+(pair<int*,int> a0,pair<int*,int> b0){ int* a=a0.first;int size_a=a0.second;int *b=b0.first;int size_b=b0.second; //a有多少位,b有多少位.返回:数组地址,有多少位 if(size_a==0&&size_b==0){ int * temp=new int [2]; temp[0]=0; return make_pair(temp,1); } int max_size=max(size_a,size_b),min_size=min(size_a,size_b); int *ans; int max_c[max_size+5],min_c[min_size+5]; ans=new int[max_size+10]; memset(ans,0,sizeof(int)*(max_size+10)); memset(max_c,0,sizeof(int)*(max_size+5)); memset(min_c,0,sizeof(int)*(max_size+5)); if(size_a>=size_b){ memcpy(max_c,a,size_a*sizeof(int)); memcpy(min_c,b,size_b*sizeof(int)); } else{ memcpy(min_c,a,size_a*sizeof(int)); memcpy(max_c,b,size_b*sizeof(int)); } for(int i=max_size-1;i>=0;--i){ if(max_size-i>min_size) ans[i+1]+=max_c[i]; else ans[i+1]+=max_c[i]+min_c[min_size+i-max_size]; } for(int i=max_size;i>=0;--i){ if(int(ans[i])>9){ ans[i-1]+=1; ans[i]-=10; } } int count_=0; for(;count_<=max_size+1;++count_){ if(ans[count_]!=0) break; } return make_pair(ans+count_,max_size+1-count_); } bool operator<(pair<int*,int> a0,pair<int*,int> b0){ if(a0.second!=b0.second) return a0.second<b0.second; else{ for(int k=0;k<a0.second;++k){ if(a0.first[k]!=b0.first[k]) return a0.first[k]<b0.first[k]; } } return true; } int m; int num[60]; int mark[60][60];//这个作为备忘数组的标志数组 pair<int*,int> note[60][60];//备忘数组不能用int的情况 pair<int*,int> V(int i,int j){//前i个数字中有j个加号的情况 if(mark[i][j]!=-1){ return note[i][j]; } else if(i<j+1){ mark[i][j]=1; int *temp=new int[10]; for(int m=0;m<10;++m) temp[m]=9; note[i][j]=make_pair(temp,10); return note[i][j]; } else if(i==j+1){ mark[i][j]=1; pair<int*,int> temp=make_pair(num+0,1); for(int m=1;m<i;++m){ temp=temp+make_pair(num+m,1); } note[i][j]=temp; return note[i][j]; } else if(j==0){ mark[i][j]=1; pair<int*,int> temp; temp.second=i; temp.first=new int[i]; for(int m=0;m<i;++m) temp.first[m]=num[m]; note[i][j]=temp; return note[i][j]; } else{//重载小于号 pair<int*,int> MIN; MIN.first=NULL;MIN.second=99999; for(int k=1;k<=i-1;++k){ if(i-k<j) break; pair<int*,int> temp; temp=V(i-k,j-1)+make_pair(num+i-k,k); //cout<<"你妈的为什么"<<i<<" "<<j<<" "<<k<<endl; if(temp<MIN) MIN=temp; } mark[i][j]=1; note[i][j]=MIN; return MIN; } } int main(){ while(cin>>m){ memset(mark,-1,sizeof(mark)); memset(num,0,sizeof(int)*60); string temp=""; cin>>temp; int n=temp.size(); for (int i=0;i<n;++i) num[i]=int(temp[i])-48;//字符转化为整形数的ascii马的差值常数为48 pair<int*,int> answer=V(n,m); for(int i=0;i<answer.second;++i) cout<<answer.first[i]; cout<<endl; } return 0; }
递归思想
每一个状态可以用数字个数i和前i位数字中使用j个+号这两个参数来实现。所求为V(n,m)
然后从最后一位简单的一次往前取最小即可(不知道二分法可不可以,这样可以降低复杂度)
C++语法:
1.对字符型数字’1’~'9’使用int进行类型转换会返回其ascii码,对映关系为 x=int(‘x’)-48 见124行
2.关于运算符的重载:
①等号的重载只能作为成员函数,本题中使用pair来操作并不能重载等于号,pair内部已经默认重载过first和second相等这种=号含义了
②重载+后也不能使用+=,类似++这些符号都不是一个东西,要分别重载
3.关于cin,getline,getchar
①cin读入字符串的时候会遇到空格停止,如果想要读入空格需要使用getline(cin,s)
②getline不会读入回车,并且自动删除回车号
③getchar可以读入回车号
④对于这个b题这样不好好给数据组数的可以使用类似117行中的方法进行停止程序
具体见
https://blog.csdn.net/qq_41289920/article/details/80542356
2757最长上升子序列
#include<cstdlib> #include<cstdio> #include<cstring> #include<iostream> using namespace std; int N; int quene[1005]; int mask[1005][10005]; int F(int n,int MAX){ if(mask[n][MAX]!=-1)//有标记 return mask[n][MAX]; else if(n==1){//只剩下前一位数 if(quene[n-1]<MAX){ mask[n][MAX]=1; return mask[n][MAX]; } else{ mask[n][MAX]=0; return mask[n][MAX]; } } else if(quene[n-1]>=MAX){ mask[n][MAX]=F(n-1,MAX); return mask[n][MAX]; } else{ mask[n][MAX]=max(1+F(n-1,quene[n-1]),F(n-1,MAX)); return mask[n][MAX]; } } int main(){ cin>>N; memset(quene,0,sizeof(int)*N); memset(mask,-1,sizeof(mask)); for(int i=0;i<N;++i) cin>>quene[i]; int answer=F(N,10010);//前N个数,最大要比quene[N-1]+1小 cout<<answer<<endl; return 0; }
递归想法:
两个参数①前几个数 ②必须要小于哪个数 MAX
时间复杂度好像有点高,后面在看看怎么优化吧
1458 Common Subsequence
A.记忆递归:
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> using namespace std; string a[2]; int mark[500][500]; int answer(int i,int j){//i为第一个字符串最后一位坐标,j为第二个字符串最后一位坐标 if(mark[i][j]!=-1) return mark[i][j]; else if(a[0][i]==a[1][j]){ if(i!=0&&j!=0){ mark[i][j]=answer(i-1,j-1)+1; return mark[i][j]; } else{ mark[i][j]=1; return 1; } } else{ if(j!=0&&i!=0){ mark[i][j]=max(answer(i,j-1),answer(i-1,j)); return mark[i][j]; } else if(j==0&&i!=0){ mark[i][j]=answer(i-1,j); return mark[i][j]; } else if(j!=0&&i==0){ mark[i][j]=answer(i,j-1); return mark[i][j]; } else{ mark[i][j]=0; return 0; } } } int main(){ a[0]="";a[1]=""; while(cin>>a[0]>>a[1]){ memset(mark,-1,sizeof(mark)); int max_ans=0; max_ans=answer(a[0].length()-1,a[1].length()-1); cout<<max_ans<<endl; } return 0; }
B.动规写法:
#include<cstdlib> #include<cstdio> #include<cstring> #include<iostream> using namespace std; string a[2]; int mark[500][500]; int main(){ a[0]="";a[1]=""; while(cin>>a[0]>>a[1]){ memset(mark,-1,sizeof(mark)); for(int i=0;i<a[0].length();++i){ if(a[0][i]==a[1][0]) mark[i][0]=1; } for(int j=0;j<a[1].length();++j){ if(a[0][0]==a[1][j]) mark[0][j]=1; } if(a[0][0]!=a[1][0]) mark[0][0]=0; //以上为初始化边界值,以下为递推计算 for(int i=0;i<a[0].length();++i){ for(int j=0;j<a[1].length();++j){ if(a[0][i]==a[1][j]){ if(i!=0&&j!=0) mark[i][j]=mark[i-1][j-1]+1; } else{ if(i!=0&&j!=0) mark[i][j]=max(mark[i][j-1],mark[i-1][j]); else if(i!=0&&j==0) mark[i][j]=mark[i-1][j]; else if(i==0&&j!=0) mark[i][j]=mark[i][j-1]; } } } cout<<mark[a[0].length()-1][a[1].length()-1]<<endl; } return 0; }
递归思路:
两个参数i(第一个字符串字节数),j(第二个字符串字节数)
如果第一个前i位和第二个前j位的最后一位相同,就1+V(i-1,j-1)否则就取MAX(V(i,j-1)和V(i-1,j))
- 点赞
- 收藏
- 分享
- 文章举报
- 动态规划问题数字三角形的(递归程序)
- 动态规划(二)暴力递归的优化之路——数字三角形最大路径和
- 算法基础之python实现动态规划中数字三角形和最长上升子序列问题
- 动态规划 :POJ 1191 棋盘分割
- 069day(动态规划例题:数字三角形和输入输出流相关的类)
- 动态规划4--最佳加法表达式
- 动态规划-最佳加法表达式
- 【基础动态规划】上升序列
- 动态规划之最长上升子序列 O(N log N)版
- 【DP】 POJ 1191 棋盘分割 记忆化搜索
- 动态规划训练19、最短路 [Help Jimmy POJ - 1661 ]
- 线段树与动态规划的结合 最长上升序列的线段树优化 正在研究
- 【DP】 POJ 1191 棋盘分割 记忆化搜索
- 动态规划 最长公共子序列 最长上升子序列 最长上升公共子序列
- 动态规划--(加法表达式)
- 动态规划--数字三角形
- 动态规划--(数字三角形 poj1163)
- 动态规划例题总结
- 动态规划6-背包问题+记忆递归
- 【算法竞赛入门经典】递归结构的动态规划 例题9-10 UVa1626