动态规划 线性dp

动态规划通常是用来解决最优化问题的。
一:斐波那契数列
对于斐波那契数列递归和非递归,可以参考这篇博客:https://mp.csdn.net/postedit/79280467
如果用递归写斐波那契数列,有很多重复计算值,导致时间复杂度大,可以用记忆搜索。记忆搜索是将已经计算过的值记录下来,即用空间换时间。

//a[]来记录每个斐波那契值
int fib(int n)
{
    if (n == 1 || n == 2)
        return a[n] = 1;
    if (a[n] == 0)
        return a[n] = fib(n - 2) + fib(n - 1);
    else //说明该值已经计算过
        return a[n];  
}

斐波那契的转换
例1:有一人要爬n阶的楼梯,他一次可以爬1阶或2阶,问要爬完这n阶楼梯,共有多少种方法?
假设我们现在在第n阶阶梯上,显然,我们上一步是在n-1阶或者n-2阶,我们可以知道,第n阶的方法=n-1阶的方法+n-2阶的方法
同样的,对于n-1阶和n-2阶我们也可以用类似的方法进行求解。
而当我们求到1阶和2阶的时候,显然方法种数分别为1、2。
所以如果f[i]表示爬到第i阶的方法数,那么
f[1]=1 f[2] = 2
f[i] = f[i - 1]+ f[i - 2]
例2:在2×n的一个长方形方格中用一个1×2的积木铺满方格输入n 输出铺放方案的总数.
例如n=3时为2×3方格,积木的铺放方案有三种如下图:
这里写图片描述
这里写图片描述
思路为:
这里写图片描述
所以排积木是斐波那契的转换。
代码如下:

//用long long是因为当n=50数值会超整型范围
long long  result[55] ;
long long jimu(int n)
{
    if (n == 1 || n == 2)
        return result[n] = n;
    if (result[n] == 0)//该值没有算过
        return result[n] = jimu(n - 1) + jimu(n - 2);
    else
        return result[n];
}
int main()
{
    int n = 0;
    fill(result, result + 55, 0);//计划搜索需要初始化
    while (scanf("%d", &n) != EOF)
    {
        printf("%lld\n", jimu(n));
    }
    system("pause");
    return 0;
}

例1:数字三角形
这里写图片描述
可以将三角形转化为下三角形即
这里写图片描述
这样就是向下走或者向右下走,求第一层到最低层路径最大值,可以先求出顶元素向下走最大还是向右下走最大,即dp[1][1]=max(dp[2,1],dp[2,2])+arr[1][1],dp[i][j]表示该位置向底层走路径的最大值,那么dp[2,1]=max(dp[3,1],dp[3,2])+arr[2,1]….直至i=n到达最底层,用记忆化搜索数字三角形代码如下:

#include<stdio.h>
#include<stdlib.h>
int n = 0;//n代表数字三角形行数
int arr[100][100] = { 0 };//arr[][]是数字三角形原始数据
int dp[100][100] = { 0 };//dp[][]是该位置到底层路径最大值
//定义成全局方便maxlen()函数操作
int maxlen(int x, int y)
{
    if (x == n)
        return arr[x][y];
    if (dp[x][y] == -1)
        return dp[x][y] = max(maxlen(x + 1, y), maxlen(x + 1, y + 1)) + arr[x][y];
    else
        return dp[x][y];

}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)//二维数组从1开始
    {
        for (int j = 1; j <= i; j++)
        {
            scanf("%d", &arr[i][j]);
            dp[i][j] = -1;//架构每个位置路径最大值初始化为-1.为记忆搜索做准备
        }
    }
    printf("%d\n", maxlen(1, 1));//指从arr[1][1]到底层路径最大值
    system("pause");
    return 0;
}

递推数字三角形

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int n = 0;//n代表数字三角形行数
    int arr[100][100] = { 0 };//arr[][]是数字三角形原始数据
    int dp[100][100] = { 0 };//dp[][]是该位置到底层路径最大值
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)//二维数组从1开始
    {
        for (int j = 1; j <= i; j++)
        {
            scanf("%d", &arr[i][j]);
            dp[i][j] = arr[i][j];//架构每个位置路径最大值初始化该位置原始值,为最底层最大值做准备
        }
    }
    for (int i = n - 1; i >= 1; i--)
    {  //底层元素认为是该位置路径最大值
        for (int j = 1; j <= i; j++)
        {
            dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + arr[i][j];
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= i; j++)
            printf("%d ", dp[i][j]);
        printf("\n");
    }
    printf("%d\n", dp[1] [1]);//指从arr[1][1]到底层路径最大值
    system("pause");
    return 0;
}

这里写图片描述
例2:滑雪
这里写图片描述

#include<stdio.h>
#include<stdlib.h>
int n = 0;
int arr[100][100];//每个点的高度
int maxlen(int x, int y)
{
    //int Max = 1;
    if (x > 0 && x <= n && y>0 && y <= n)
    {
        int Max = 1;
        //假如该位置上边点到坡底最长为m,该位置到坡底最长距离为m+1
        if (arr[x - 1][y] < arr[x][y])//上
            Max = max(maxlen(x - 1, y) + 1, Max);
        if (arr[x + 1][y] < arr[x][y])
            Max = max(maxlen(x + 1, y) + 1, Max);//下
        if (arr[x][y + 1] < arr[x][y])//右
            Max = max(maxlen(x, y + 1) + 1, Max);
        if (arr[x][y - 1] < arr[x][y]) //左
            Max = max(maxlen(x, y - 1) + 1, Max);
        return Max;
    }
    return 0; //注意递归时,int类型返回时需要退出该函数需要有返回值,通常是0,对该题也是0
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            scanf("%d", &arr[i][j]);
        }
    }
    int Maxlen = 0;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            Maxlen = max(Maxlen, maxlen(i, j));
        }
    }
    printf("**************\n");
    printf("%d\n", Maxlen);
    system("pause");
    return 0;
}

这里写图片描述
如果是用记忆化搜索代码如下:

int n = 0;
int arr[100][100];//每个点的高度
int dp[100][100]; //每个点到坡底的最长距离
int maxlen(int x, int y)
{
    if (x > 0 && x <= n && y > 0 && y <= n)
    {
        if (dp[x][y] != 0)
            return dp[x][y];
        dp[x][y] = 1;
        if (arr[x - 1][y] < arr[x][y])//上
            dp[x][y] = max(dp[x][y], maxlen(x - 1, y) + 1);
        if (arr[x + 1][y] < arr[x][y])//下
            dp[x][y] = max(dp[x][y], maxlen(x + 1, y) + 1);
        if (arr[x][y + 1] < arr[x][y])//右
            dp[x][y] = max(dp[x][y], maxlen(x, y + 1) + 1);
        if (arr[x][y - 1] < arr[x][y]) //左
            dp[x][y] = max(dp[x][y], maxlen(x, y - 1) + 1);
        return dp[x][y];
    }
    return 0;
}
    //但是首先得将dp[i][j]初始化为0,如下
    for (int j = 1; j <= n; j++)
    {
            scanf("%d", &arr[i][j]);
            dp[i][j] = 0;//为后面记忆化搜索做准备
    }       

线性dp
一:最长不降序子序列的长度
这里写图片描述

#include<stdio.h>
#include<iostream>
using namespace std;
const int maxn = 1005;
int a[maxn];
int dp[maxn];
int main()
{
    int n = 0;
    while (scanf("%d", &n) != EOF)
    {
        //将数据输入
        for (int i = 0; i < n; i++)
        {
            scanf("%d", &a[i]);
        }
        //以i结尾的数据最大不降序子序列长度
        for (int i = 0; i < n; i++)
        {
            dp[i] = 1;
            for (int j = 0; j < i; j++)
            {
                if (a[j] <= a[i])
                {
                    dp[i] = max(dp[j] + 1, dp[i]);
                }
            }
        }
        //输出最大不降序子序列长度
        int maxlen = 0;
        for (int i = 0; i < n; i++)
        {
            maxlen = max(dp[i], maxlen);
        }
        printf("%d\n", maxlen);
    }
    system("pause");
    return 0;
}

例1:A国部署的反导系统遇到了一个致命BUG,那就是每一次发射的拦截导弹的飞行高度都将只能小于等于上一枚导弹的飞行高度,第一次发射的拦截导弹的飞行高度可以看作是足够大。对于A国,这是一件很严重的问题,这意味着A国的防空系统面临空前危机。通过对A国的军事部门计算机的入侵,A国还不知道敌对国B国刚才已经发现了这项BUG。更不知道,在这项BUG的报告书上交到B国空军司令部那一刻,三分钟后B国的全体高级空军军官已经在作战室讨论作战方案。如果战争真的开始,B国将依次派出n架战斗机A国将依次发射拦截导弹,这n架飞机的飞行高度分别是h1h2h3…..hn。B国将要充分利用这项漏洞,考虑到这么一种情况,假设只要A国的导弹的飞行高度大于等于B国飞机就能百分之百地锁定并击落,那么B国,最少将会有几架不被击落飞机?
这里写图片描述
最少几架不被击落,即最多飞机被击落,那么该题可以转化求最长不升序子序列长度。
代码如下:

#include<stdio.h>
#include<iostream>
using namespace std;
int main()
{
    int T = 0;
    scanf("%d", &T);
    int a[20005] = { 0 };
    int dp[2005] = { 0 };
    while (T--)
    {
        int n = 0;
        scanf("%d", &n);
        for (int i = 0; i < n; i++)
        {
            scanf("%d", &a[i]);
        }
        for (int i = 0; i < n; i++)
        {
            dp[i] = 1;
            for (int j = 0; j < i; j++)
            {
                if (a[j] >= a[i])
                {
                    dp[i] = max(dp[j] + 1, dp[i]);
                }
            }
        }
        int maxlen = 0;
        for (int i = 0; i < n; i++)
        {
            maxlen = max(dp[i], maxlen);
        }
        printf("%d\n", n - maxlen);
    }
    system("pause");
    return 0;
}

最长公共子序列
这里写图片描述
这里写图片描述
当j=0或i=0初始化为0:如果j=0,即X中没有元素,如果i=0,即Y中没有元素。
例1:渣渣辉从小的梦想就是当一名警察,做一名出色的反恐精英。
但是事与愿违,他现在只能天天在家里打 CF (codeforce) 了。
我们知道,CF上经常有比赛,渣渣辉这么热爱CF,当然一次也没有落下。
比赛规则是这样的:
1.有 N 种子弹型号,编号分别为 1,2 … N。
2.有 N 种恐怖分子类型,编号也分别为1,2 … N。
3.敌人有 M 个,子弹也会有 M 个。
4.只有一种子弹打中同种编号类型的敌人时,得分才会加 1 分。
5.子弹已经按照输入顺序装填,恐怖分子也按照输入顺序出现。
你能帮助渣渣辉打到最高分吗?
(渣渣辉射击百发百中,他可以放空枪,也可以选择不射击某名恐怖分子)
这里写图片描述
递推代码:

#include<include.h>
#include<stdlib.h>
const int Maxn = 100;
const int Maxm = 100;
int diren[Maxm];
int zidan[Maxm];
int dp[Maxm][Maxm];
int main()
{
    int N = 0; //N 种类型的敌人和子弹
    int M = 0; //M 个敌人和子弹
    scanf("%d %d", &N, &M);
    for (int i = 1; i <= M; i++)
    {
        scanf("%d", &diren[i]);
    }
    for (int i = 1; i <= M; i++)
    {
        scanf("%d", &zidan[i]);
    }

    for (int i = 0; i <= M; i++)
    {
        dp[0][i] = dp[i][0] = 0;//第i行和第j列初始化为0
    }
    for (int i = 1; i <= M; i++)
    {
        for (int j = 1; j <= M; j++)
        {
            if (diren[i] == zidan[j])
            {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            }
            else
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
        }
    }
    printf("%d\n",dp[M][M]);//最后一个肯定最大
    system("pause");
    return 0;    
}

递归代码:

#include<include.h>
#include<stdlib.h>
const int Maxn = 100;
const int Maxm = 100;
int diren[Maxm];
int zidan[Maxm];
int dp[Maxm][Maxm];
int maxlen(int x,int y)
{
    if (x == 1)//递归出口
        return 0;
    if (dp[x][y] == 0)
    {
        if (diren[x] == zidan[y])
            return dp[x][y] = maxlen(x - 1, y - 1) + 1;
        else
            return dp[x][y] = max(maxlen(x , y - 1), maxlen(x - 1, y ));
    }   
    return dp[x][y];        
}
int main()
{
    int N = 0; //N 种类型的敌人和子弹
    int M = 0; //M 个敌人和子弹
    scanf("%d %d", &N, &M);
    for (int i = 1; i <= M; i++)
    {
        scanf("%d", &diren[i]);
    }
    for (int i = 1; i <= M; i++)
    {
        scanf("%d", &zidan[i]);
    }
    for (int i = 0; i <= M; i++)
    {
        for (int j = 0; j <= M; j++)
            dp[i][j] = 0;//为记忆化搜索做准备
    }
    printf("%d", maxlen(M,M ));
    system("pause");
    return 0;
}

这里写图片描述
最大连续子序列和
这里写图片描述
递推代码:

#include<stdio.h>
#include<stdlib.h>
const int maxn = 1000;
int a[maxn], dp[maxn]; 
int main()
{
    int n = 0;
    while (scanf("%d", &n) != EOF)
    {
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
            dp[i] = a[i];//初始化为本身
        }
        for (int i = 2; i <= n; i++)
        {
            dp[i] = max(dp[i - 1] + a[i], a[i]);
        }
        int maxsum = 0;
        for (int i = 1; i <= n; i++)
        {
            maxsum = max(maxsum, dp[i]);
        }
        printf("%d\n", maxsum);
    }
}

递归代码:

#include<stdio.h>
#include<stdlib.h>
#include<climits>
const int maxn = 1000;
int a[maxn], dp[maxn];
int maxsum(int x)
{
    if (x == 0)//递归出口
        return 0;
    if (dp[x] == INT_MIN)
        return dp[x]=max(maxsum(x - 1) + a[x], a[x]);
    return dp[x];
}
int main()
{
    int n = 0;
    while (scanf("%d", &n) != EOF)
    {
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
            dp[i] = INT_MIN;//有符号整型最小值
        }
        for (int i = 2; i <= n; i++)
        {
            dp[i] = maxsum(i);
        }
        int maxsum = 0;
        for (int i = 1; i <= n; i++)
        {
            maxsum = max(maxsum, dp[i]);
        }
        printf("%d\n", maxsum);
    }
    system("pause");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/sophia__yu/article/details/81192491