算法-动规

例子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;
}

 

posted @ 2018-05-22 02:00  Latticeeee  阅读(280)  评论(0编辑  收藏  举报