算法-动规
例子1 数字三角形
#include <iostream> #include <algorithm> #define MAX 101 using namespace std; //数字三角形 从顶端到底边数字之和最大 求最大和 int D[MAX][MAX]; //存放数字三角形 int n; int MAXsum(int i, int j){ //从第i行dij个数字往下找,最大的和 if(i==n){ //最后一行 return D[i][j]; } int x = Maxsum(i+1,j); int y = Maxsum(i+1,j+1); return D[i][j]+max(x,y); } int main(){ int i,j; cin>>n; for(i=1;i<=n;j++){ for(j=1;j<=i;j++){ cin>>D[i][j]; } } cout<<Maxsum(1,1); }
改进后:
#include <iostream> #include <algorithm> #include <cstring> #define MAX 101 using namespace std; //数字三角形 从顶端到底边数字之和最大 求最大和 int D[MAX][MAX]; //存放数字三角形 int n; int R[MAX][MAX]; int Maxsum(int i, int j){ //从第i行dij个数字往下找,最大的和 if(i==n){ //最后一行 return D[i][j]; } if(R[i][j]!=-1){ return R[i][j]; } int x = Maxsum(i+1,j); int y = Maxsum(i+1,j+1); R[i][j]= D[i][j]+max(x,y); return R[i][j]; } int main(){ int i,j; cin>>n; for(i=1;i<=n;i++){ for(j=1;j<=i;j++){ cin>>D[i][j]; R[i][j]=-1; } } cout<<Maxsum(1,1); }
改为从最后一行往上递推
#include <iostream> #include <algorithm> #include <cstring> #define MAX 101 using namespace std; //数字三角形 从顶端到底边数字之和最大 求最大和 int D[MAX][MAX]; //存放数字三角形 int n; int R[MAX][MAX]; //从最后一行往上推 int main(){ int i,j; cin>>n; for(i=1;i<=n;i++){ for(j=1;j<=i;j++){ cin>>D[i][j]; R[i][j]=0; } } //拷贝三角形的最后一行到R 代表最后一行往下找最大和就是每个元素本身 for(int i=1;i<=n;i++){ R[n][i]= D[n][i]; } for(int i=n-1;i>=1;i--){ //由倒数第二行往上一层一层填满R for(int j=1;j<=i;j++){ //每一行从前到后 R[i][j]=max(R[i+1][j],R[i+1][j+1])+D[i][j]; } } cout<<R[1][1]; }
空间优化
#include <iostream> #include <algorithm> #include <cstring> #define MAX 101 using namespace std; //数字三角形 从顶端到底边数字之和最大 求最大和 int D[MAX][MAX]; //存放数字三角形 int n; int R[MAX]; //从最后一行往上推 int main(){ int i,j; cin>>n; for(i=1;i<=n;i++){ R[i]=0; for(j=1;j<=i;j++){ cin>>D[i][j]; } } //拷贝三角形的最后一行到R 代表最后一行往下找最大和就是每个元素本身 for(int i=1;i<=n;i++){ R[i]= D[n][i]; } for(int i=n-1;i>=1;i--){ //由倒数第二行往上一层一层填满R for(int j=1;j<=i;j++){ //每一行从前到后 R[j]=max(R[j],R[j+1])+D[i][j]; } } cout<<R[1]; }
空间再次优化
#include <iostream> #include <algorithm> #include <cstring> #define MAX 101 using namespace std; //数字三角形 从顶端到底边数字之和最大 求最大和 int D[MAX][MAX]; //存放数字三角形 int n; //从最后一行往上推 int main(){ int i,j; cin>>n; for(i=1;i<=n;i++){ for(j=1;j<=i;j++){ cin>>D[i][j]; } } for(int i=n-1;i>=1;i--){ //由倒数第二行往上一层一层填满R for(int j=1;j<=i;j++){ //每一行从前到后 D[n][j]=max(D[n][j],D[n][j+1])+D[i][j]; } } cout<<D[n][1]; }
例子2 最长上升子序列
#include <iostream> #include <algorithm> #include <cstring> #define MAX 101 using namespace std; //动规问题解题方式 //将原问题分解为子问题 子问题要与原问题相似 并且规模变小 //确定状态 以及确定状态需要几个变量 //确定初始状态的值 其他状态由这些初始状态开始求得 //确认状态转移方程 即如何从已知状态推出其他状态的值 //动规能解决的问题的特点是 问题具有最优子结构和无后效性 //最长上升子序列 上升子序列是一个序列里值逐渐递增的非连续子序列 // 对于给定序列,求最长上升子序列 //子问题: 以a(k)为终点的最长上升子序列的长度 将原问题分解成了n(序列长度)个子问题 //只需要从这n个子问题中的答案中,选取最大的 //状态 元素a(k)的下标k 第k个元素 //初始状态 k=1 时,值为1 状态转移方程 遍历序列 1-k之间的所有元素,满足条件: //1. a(i)< a(k) 2.i对应的状态值是1-k中最大的 那么a(k)= a(i)+1 否则a(k)=1; const int MAXN=1010; int a[MAXN];//存放序列 int Maxlen[MAXN];// Maxlen(i)存放以a(i)为终点的最长上升子序列的序列长度 int main(){ int n; cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; } Maxlen[1]=1;//初始状态 int MAXLEN=1; for(int i=2;i<=n;i++){ int maxlen=1; for(int j=1;j<i;j++){ if(a[j]<a[i]&& Maxlen[j]>maxlen){ maxlen=Maxlen[j]; } Maxlen[i]=maxlen+1; } if(Maxlen[i]>MAXLEN){ MAXLEN = Maxlen[i]; } } cout<<MAXLEN<<endl; }
人人为我型
#include <iostream> #include <algorithm> #include <cstring> #define MAX 101 using namespace std; //动规问题解题方式 //将原问题分解为子问题 子问题要与原问题相似 并且规模变小 //确定状态 以及确定状态需要几个变量 //确定初始状态的值 其他状态由这些初始状态开始求得 //确认状态转移方程 即如何从已知状态推出其他状态的值 //动规能解决的问题的特点是 问题具有最优子结构和无后效性 //最长上升子序列 上升子序列是一个序列里值逐渐递增的非连续子序列 // 对于给定序列,求最长上升子序列 //子问题: 以a(k)为终点的最长上升子序列的长度 将原问题分解成了n(序列长度)个子问题 //只需要从这n个子问题中的答案中,选取最大的 //状态 元素a(k)的下标k 第k个元素 //初始状态 k=1 时,值为1 状态转移方程 遍历序列 1-k之间的所有元素,满足条件: //1. a(i)< a(k) 2.i对应的状态值是1-k中最大的 那么a(k)= a(i)+1 否则a(k)=1; const int MAXN=1010; int a[MAXN];//存放序列 int Maxlen[MAXN];// Maxlen(i)存放以a(i)为终点的最长上升子序列的序列长度 int main(){ int n; cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; Maxlen[i]=0; } Maxlen[1]=1; for(int i=1;i<=n;i++){ //往后更新 for(int j=i+1;j<=n;j++){ if(a[j]>a[i]){ Maxlen[j]=Maxlen[i]+1; } } } int maxlen=1; for(int i=1;i<=n;i++){ if(Maxlen[i]>maxlen){ maxlen=Maxlen[i]; } } cout<<maxlen<<endl; }
例子3 最长公共子序列
#include <iostream> #include <algorithm> #include <cstring> #define MAX 101 using namespace std; //最长公共子序列 //给定两个字符串,求出最长公共子序列的长度, //子序列中的每个字符都能在两个原串中找到 并且每个字符的先后顺序和原串中一致 //子问题:两个字符串左边的i 和 j 个字符构成的字符串的最长公共子序列长度 Maxlen(i,j) //状态 i和j //初始状态 Maxlen(0,n) = Maxlen(n,0)=0 ; string s1,s2; int MAXLEN[100][100]; int Maxlen(int i,int j){ if(i==0 || j==0){ return 0; } if(MAXLEN[i][j]!=-1){ return MAXLEN[i][j]; } if(s1[i-1]==s2[j-1]){ MAXLEN[i][j]=Maxlen(i-1,j-1)+1; } else{ MAXLEN[i][j]=max(Maxlen(i,j-1),Maxlen(i-1,j)); } return MAXLEN[i][j]; } int main(){ memset(MAXLEN,0xff,sizeof(MAXLEN)); cin>>s1>>s2; int len1=s1.size(); int len2=s2.size(); cout<<Maxlen(len1,len2); }
例子4 最佳加法表达式
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #define MAX 101 using namespace std; //最佳加法表达式 //由1-9数字组成的数字串,将m个加号插入到数字串中,在可能形成的表达式中,最小的表达式的值 //子问题: V(0,n)=n个数字构成的整数 状态转移函数V(m,n)=V(m-1,i)+Num(i+1,n) 在i之后插入最后一个加号,遍历选出最小的 string s; int num[100][100]; int NUM(int i,int j){ //string[i] 到string[j]之间的字符串转换为整数 不包括string j if(num[i][j]!=-1){ return num[i][j]; } int len=j-i;//位数 int result=0; for(int r=len-1;r>=0;r--){ result += (s[i]-'0')*pow(10,r); i++; } num[i][j]=result; return num[i][j]; } int v[100][100]; int V(int m,int n){ //将m个加号插到n个数字串中 int r=1<<30; if(m==0){ return NUM(0,n); } if(m>n-1){ return r; } if(v[m][n]!=-1){ return v[m][n]; } for(int i=m;i<=n-1;i++){ //将m-1个加号插到i个数字之中,i从m开始到n-1 r = min(r,V(m-1,i)+NUM(i,n)); } v[m][n]=r; return r; } int main(){ memset(num,0xff,sizeof(num)); memset(v,0xff,sizeof(v)); cin>>s; int m; cin>>m; cout<<V(m,s.size()); }
例子5 神奇的口袋
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #define MAX 101 using namespace std; //神奇的口袋 //n个物品 有不同的体积 凑成40 的凑法 int goods[100]; int WAYS[100][100]; int ways(int w,int k){ //从前k种物品中选择 凑成总值为w if(w==0){ return 1; } if(k<=0){ return 0; } if(WAYS[w][k]!=-1){ return WAYS[w][k]; } WAYS[w][k]=ways(w,k-1)+ways(w-goods[k],k-1); return WAYS[w][k]; } int main(){ memset(WAYS,0xff,sizeof(WAYS)); int n; cin>>n; for(int i=1;i<=n;i++){ cin>>goods[i]; } cout<<ways(40,n); }
动规解法
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #define MAX 101 using namespace std; //神奇的口袋 //n个物品 有不同的体积 凑成指定值 的凑法 int goods[100]; int ways[100][100]; //ways[i][j] 前j种物品种凑出i int main(){ memset(ways,0,sizeof(ways)); int n; //n件物品 cin>>n; int r;//目标值 cin>>r; for(int i=1;i<=n;i++){ cin>>goods[i]; ways[0][i]= 1; //边界状态 } ways[0][0]=1; for(int w=1;w<=r;w++){ for(int k=1;k<=n;k++){ ways[w][k]=ways[w][k-1]; if(w-goods[k]>=0){ //如果有可能取第k件物品 ways[w][k] += ways[w-goods[k]][k-1]; } } } cout<<ways[r][n]; }
我为人人解法
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #define MAX 101 using namespace std; //神奇的口袋 //n个物品 有不同的体积 凑成指定值 的凑法 //我为人人解法 int main(){ int MaxSum[100]; memset(MaxSum,0,sizeof(MaxSum)); int n; //n件物品 cin>>n; int r;//目标值 cin>>r; int input; for(int i=1;i<=n;i++){ cin>>input; for(int j=r;j>=1;j--){ //由后往前遍历 因为可能要用到后面的值 if(MaxSum[j]>0 && j+input<=r){ //如果有sum[j]种方法可以凑成j,那么多了input,凑成j+input的方法就多了 sum[j]种 MaxSum[j+input] += MaxSum[j]; } } MaxSum[input]++; } cout<<MaxSum[r]; }
例子6 背包问题
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #define MAX 101 using namespace std; //背包问题 //N件物品 和 容积为M 的书包,第i件物品的体积w[i] 价值为d[i] //求解将哪些物品装入背包可使价值总和最大 //状态: F[i][j]表示取前i种物品,使它们总体积不超过j的最优取法的价值总和 //边界 如果第一件物品的体积小于j 那么F[1][j]=d[1] 否则F[1][j]=0 //状态转移方程 取或者不取第i件物品 取最大值 int main(){ int N,M; cin>>N>>M; //N<100 M<100 int w[100]; int d[100]; int F[100][100]; memset(F,0,sizeof(F)); for(int i=1;i<=N;i++){ cin>>w[i]>>d[i]; } for(int j=1;j<=M;j++){ //遍历所有体积 if(w[1]<=j){ //边界条件 从前1种物品中取 使得总体积不超过j 的价值总共和 F[1][j]=d[1]; } else{ F[1][j]=0; } } for(int i=2;i<=N;i++){ //遍历剩下的物品 for(int j=1;j<=M;j++){ //遍历剩下的体积 if(j-w[i]>=0){ F[i][j]=max(F[i-1][j],F[i-1][j-w[i]]+d[i]); } else{ F[i][j]=F[i-1][j]; } } } cout<<F[N][M]; }
节省空间 滚动一维数组解法
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #define MAX 101 using namespace std; //背包问题 //N件物品 和 容积为M 的书包,第i件物品的体积w[i] 价值为d[i] //求解将哪些物品装入背包可使价值总和最大 //状态: F[i][j]表示取前i种物品,使它们总体积不超过j的最优取法的价值总和 //边界 如果第一件物品的体积小于j 那么F[1][j]=d[1] 否则F[1][j]=0 //状态转移方程 取或者不取第i件物品 取最大值 //观察到状态转移方程只使用了上一行的数组 因此改变遍历方向即可 缩减为1维数组 int main(){ int N,M; cin>>N>>M; //N<100 M<100 int w[100]; int d[100]; int F[100]; memset(F,0,sizeof(F)); for(int i=1;i<=N;i++){ cin>>w[i]>>d[i]; } for(int j=1;j<=M;j++){ //遍历所有体积 if(w[1]<=j){ //边界条件 从前1种物品中取 使得总体积不超过j 的价值总共和 F[j]=d[1]; } else{ F[j]=0; } } for(int i=2;i<=N;i++){ //遍历剩下的物品 for(int j=M;j>=1;j--){ //遍历剩下的体积 if(j-w[i]>=0){ F[j]=max(F[j],F[j-w[i]]+d[i]); } } } cout<<F[M]; }
例子7 滑雪问题
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> using namespace std; //滑雪问题 // 二维数组给出一个区域高度 //一个人可以从这个点滑向上下左右相邻的四个点之一 //求最长区域的长度 (往下滑了多少个点) //状态:L(i,j)从 点(i,j)点出发的滑行长度 //一个点(i,j)如果周围没有比它低的点,L(i,j)=1 //否则L(i,j)等于周围四个点的中 比(i,j)低,且L值最大的点的L值+1 //知道一个点必须要周围四个点的L值 //用递归做 int M[200][200];//区域高度 int l[200][200];//l[i][j]代表(i,j)点出发的最长滑行长度 int R,C;//R行 C列 int L(int i,int j){ if(M[i][j] <= M[i-1][j] && M[i][j] <= M[i+1][j] && M[i][j] <= M[i][j-1] && M[i][j] <=M[i][j+1]){ return 1; } if(l[i][j]!=-1){ return l[i][j]; } int result=1; if(M[i][j] >M[i-1][j]) result = max(result,L(i-1,j)+1); if(M[i][j] >M[i+1][j]) result = max(result,L(i+1,j)+1); if(M[i][j] >M[i][j+1]) result = max(result,L(i,j+1)+1); if(M[i][j]> M[i][j-1]) result = max(result,L(i,j-1)+1); l[i][j]=result; return l[i][j]; } int main(){ cin>>R>>C; memset(l,0xff,sizeof(l)); int INFINITE = 1<<30; for(int i=1;i<=R;i++){ for(int r=1;r<=C;r++){ cin>>M[i][r]; } M[i][0]=INFINITE;//边界初始化 M[i][C+1]=INFINITE; } for(int i=1;i<=C;i++){ M[0][i]=INFINITE; M[R+1][i]=INFINITE; } int MAX=1; for(int i=1;i<=R;i++){ for(int j=1;j<=C;j++){ MAX=max(MAX,L(i,j)); } } cout<<MAX; }