• 动态规划(DP)01


      关于他们的思想,这里就不再罗嗦了,直接 show you my code ,看题讨论 。

    题目1:自然是最最经典的塔类问题啦(数字之塔 )

    有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
    这里写图片描述
    Input
    输入数据首先包括一个整数C,表示数据的个数。
    每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。
    Output
    对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。
    Sample Input
    1
    5
    7
    3 8
    8 1 0
    2 7 4 4
    4 5 2 6 5
    Sample Output
    30

    解题思路:这类问题从塔的底层开始看起,从倒数第二层计算找到一个最大的和(倒数第二层与倒数第一层左右的和),比如:在这道题中,可以找到( 21,9,25,28,19,13,9,21),那么倒数第二层就会变成了(21,28,19,21)可以看出是目前从10->18最优,依次类推,下一步就会得到(31,38,34,25,27,29),那么倒数第三层就变成了(38,34,29),再下一步得到(50,46,49,44),,那么倒数第四层就变成了(50,49),以此类推即可。重点就是(局部最优,不一定整体最优)

    #include <iostream>
    #include <string.h>
    #include<algorithm>
    using namespace std;
    #define TT 200 //整数 N(1 <= N <= 100),表示数塔的高度
    int F[TT][TT];
    int DP(int row)
    {
        for (int i = row - 2; i >= 0; i--)
        {
            for (int j = 0; j <= i; j++)
                F[i][j] = max(F[i][j] + F[i + 1][j], F[i][j] + F[i + 1][j + 1]);
        }
        return F[0][0];
    }
    int main(void)
    {
        int C, N;
        while (cin >> C) 
        {
            for (int i = 0; i < C; i++)
            {
                memset(F, -1, sizeof(F));
                cin >> N;                   
                for (int j = 1; j <= N; j++) 
                {
                    for (int k = 0; k < j; k++) 
                        cin >> F[j - 1][k];
                }
                cout << DP(N) << endl;
            }
        }
    }

    题目2:B - 兑换硬币 + (DP || 数学)

    在某个国家,只有 1 元、2 元和 3 元面值的三种硬币。现在你持有 N 元,想
    把它全部兑换为硬币,请问有多少种兑换方法呢?

    输入
    输入的数据有多组,每组数据在一行内有一个正整数 N。

    输出
    对于每组数据,在一行内输出将 N 元换成硬币的方法数。

    样例输入
    2934
    12553

    样例输出
    718831
    13137761

    DP 方法求解:

    思路:递推即可

    #include <iostream>
    #include <vector>
    using namespace std;
    int DP(int amount)
    {
        vector<int> F(amount + 1, 1);
        for (int i = 2; i <= 3; ++i)
        {
            for (int j = i; j <= amount; ++j)
            {
                F[j] += F[j - i];  //F[k]表示的兑换方法数
            }
        }
        return F[amount];
    }
    int main(void)
    {
        int N = 0;
        while (cin >> N)
        {
            cout << DP(N) << endl;
        }
        return 0;
    }

    数学方法求解:有了数学,真的可以为所欲为。这句话真的没有一点毛病。先上AC代码看看:

    #include <iostream>
    #include <vector>
    using namespace std;
    int main(void)
    {
        int N = 0;
        int temp ,Sum ,MM ;
        while (cin >> N)
        {
            temp = N / 3; //不仅表示有多少个三,还表示有3和1的组合的种类与多少种
            Sum = N / 3 + 1;  // +1代表什么?代表用 1 所构成的兑换方法数
            for (int i = 0; i <= temp; ++i)
            {
                MM = (N - i * 3) / 2; /*  “/2”是什么意思?  */
                /* 代表用2和1的组合去换拿掉一个3时剩下的数,可是,
                这又是为什么呐?为什么要拿掉一个三?不拿掉其他的?
                 */ 
                Sum += MM;
            }
            cout << Sum << endl;
        }
        return 0;
    }

    情景叙述:这里我们就以10元钱为例,10元可以只由3和1的组合构成10/3种([3,3,3,1],[3,3,111,1],[3,111,111,1]),而由1构成的只有一种([111,111,111,1]),当然,在这时,我们也知道了它有多少(N/3)个三,这时我们求它只由2和1构成的数量(N/2),然后我们拿出来一个三(其实就是这个三不变了,其余的用2和1的组合代替),以此类推,我们再拿出来一个三,再拿出来一个三,再拿出来一个三,直到把三拿完即可。

    PS:感觉这个结论,是具有普适性的,以后遇到再进行更加一步的证明。有待论证。

    题目3,4:都是最长公共子序列的变式,以前写过,这里就不说了 。不会的可以点 这里

    题目5:E - 超级跳跃 Remake

    这里写图片描述

    输入
    输入的数据有多组,每组数据首先是两个正整数 N 和 M (1 ≤ N, M ≤ 100)
    表示在一个 N × N 的区域内进行游戏,且超级跳跃只能移动到距离为 M 的区
    域中。接下来有 N 行,每行有 N 个以空格为分dp
    若 N 和 M 均cout << 等,表示输入结束,每组数据,在一行内输出一个正整数,表示游戏中可获得的最大价值。

    样例输入
    3 1
    1 2 5
    10 11 6
    12 12 7
    -1 -1

    样例输出
    37 */

    解题思路:DFS+DP。DFS通过递归的手段从后向前找寻答案,而我们一般的DP从前向后寻找答案。这道题则采用 DFS+DP(记忆化搜索)的手段去解决。当前状态的值等于 Max(下一个状态1,下一个状态2,下一个状态3,下一个状态4 。。。)这里需要通过递归获得。

    DP[i][j] 表示的就是从[i][j] 点开始所能吃到的最大奶酪数
    
    #include <iostream>
    #include <string.h>
    #include <algorithm>
    using namespace std;
    const int TT = 101;
    int map[TT][TT] = {0};
    int dp[TT][TT] = {0};
    /*用dp[i][j]表示以(i,j)为起点进行dfs的结果,从而下次扫到(i,j)时就不需要再扫一遍了。*/
    int M = 0, N = 0;
    int dir[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; //右,下,左,上
    bool check(int x, int y, int xx, int yy)
    {
        if (xx < 0 || xx > N || yy < 0 || yy > N)
            return false;
        if (map[xx][yy] <= map[x][y]) // 判断条件
            return false;
        return true;
    }
    int DFS(int x, int y)
    {
        if (dp[x][y] > 0)
            return dp[x][y];
        int result = 0;
        for (int i = 0; i < 4; i++)
        {
            for (int j = 1; j <= M; j++)
            {
                int xx = x + dir[i][0] * j;
                int yy = y + dir[i][1] * j;
                if (check(x, y, xx, yy))
                {
                    result = max(result, DFS(xx, yy));
                    //printf("result ==  %d 
    ", result);
                }
            }
        }
        dp[x][y] = result + map[x][y];//从dp[x][y]开始能吃到的最大奶酪量
        /*可以走各个点的最大值中最大的那个+当前点的数值 = 当前点的最大值*/
        printf("dp[%d][%d] == %d
    ", x, y, dp[x][y]);
        return dp[x][y];
    }
    int main(void)
    {
        while (cin >> N >> M)
        {
            if (N == -1 && M == -1)
                return 0;
            memset(map, 0, sizeof(map));
            memset(dp, 0, sizeof(dp));
            for (int i = 0; i < N; i++)
            {
                for (int j = 0; j < N; j++)
                {
                    cin >> map[i][j];
                }
            }
            cout << DFS(0, 0) << endl;
        }
    }

    记忆化搜索与DP的区别:

    1. DP是从下向上,而记忆化搜索是从上向下的

    2. DP是从下向上,为了求到最终结果需要把过程中所有的值都保存下来,以便下一步可能会使用,而因为记忆化搜索是从上向下的,所以求解过程求的都是需要的;也就是说不需要的值并没有求

    3. 记忆化搜索使用递归实现的,而DP是使用迭代实现的

    如果一个dp[i][j]的值已经求过,使用DP直接调用即可;而使用记忆化搜索则要进入递归中去求,而这个递归很有可能是多重的

    题目6:F - 超级跳跃 2 + 排队问题

    广受好评的《超级跳跃》游戏终于出了新作品,你作为它的粉丝已经迫不及
    待的想要购买了。当你到达电玩店时,发现店前已经排起了长队,加上你一
    共有 N 个人之多!每个人单独购买自己所需要的产品所需 Ki 秒,也可以选择
    和排在自己前面的那个人合作,这样的话则需要 Si 秒。现在是早上 8 点,若
    这 N 个人采用了最快的方式买完了自己所需的产品,那么买完的时候是什么
    时间呢?

    输入
    输入的数据首先是一个整数 C,表示有 C 组输入数据。每组数据首先是一个
    正整数 N(1 ≤ N ≤ 10),然后在下一行内有 N 个整数 K1,K2 … KN,表示 N
    个人单独购买需要的时间,接下来有 N – 1 个整数 S1,S2 … SN-1,表示每个
    人和前面那个人一起购买需要的时间。

    输出
    对于每组数据,在一行内输出 N 个人最快在何时全部完成购买。
    输出格式为 HH:MM:SS am/pm,如开始时间就表示为 08:00:00 am,下午 2
    时则表示为 14:00:00 pm。

    样例输入
    2
    2
    20 25
    40
    1
    8

    样例输出
    08:00:40 am
    08:00:08 am

    解题思路:状态转移方程叙述:第n个人所用的时间就是 Min(前一个人所用的时间+第n个人单独用的时间,前n-2个人所用的时间+第n-1个人和第n个人时间和)

    #include <iostream>
    #include <string.h>
    #include <algorithm>
    #include <climits>
    using namespace std;
    const int TT = 2000;
    int F[2][TT] = {0}; // 2 行 ,最大是 N 列
    int dp[TT] = {0};
    int DP(int n)
    {
        dp[0] = F[1][0];
        dp[1] = min(F[0][0], dp[0] + F[1][1]);
    
        for (int i = 2; i < n; i++)
            dp[i] = min(dp[i - 1] + F[1][i], dp[i - 2] + F[0][i - 1]);
    
        /*状态转移方程叙述:第n个人所用的时间就是 
        Min(前一个人用的时间+第n个人单独用的时间,前n-2个人所用的时间+第n-1个人和第n个人的时间和)*/
        return dp[n - 1];
    }
    int main(void)
    {
        int C, N;
        while (cin >> C)
        {
            for (int k = 0; k < C; k++)
            {
                cin >> N;
                memset(F, 0, sizeof(F));
                for (int i = 0; i < N; i++)
                    cin >> F[1][i];
                for (int j = 0; j < N - 1; j++)
                    cin >> F[0][j];
    
                int temp = DP(N);
    
                int second = temp % 60;
                int hour = temp / 60 / 60 + 8;
                temp = temp / 60;
                int minutes = temp % 60;
                if (hour >= 12)
                    printf("%02d:%02d:%02d pm
    ", hour, minutes, second);
                else
                    printf("%02d:%02d:%02d am
    ", hour, minutes, second);
            }
        }
    }
    
  • 相关阅读:
    Spring AOP 详解
    java 线程的几种状态
    Atomic
    ConcurrentHashMap原理分析
    MySQL存储过程详解 mysql 存储过程
    spring-定时器(2)
    spring-定时器(1)
    spring-线程池(3)
    spring-线程池(2)
    spring-线程池(1)
  • 原文地址:https://www.cnblogs.com/Tattoo-Welkin/p/10335267.html
Copyright © 2020-2023  润新知