第二十六天:动态规划--五大类

一、递推求解

T1

N 阶楼梯上楼问题

时间限制:1 秒
内存限制:128 兆
特殊判题:否
题目描述: N 阶楼梯上楼问题:一次可以走两阶或一阶,问有多少种上楼方式。 (要求采用非递归)

输入: 输入包括一个整数 N,(1<=N<90)。

输出: 可能有多组测试数据,对于每组数据,输出当楼梯阶数是 N 时的上楼方式个数。

样例输入:
4

样例输出:
5

//
//  main.cpp
//  Floors
//
//  Created by Apple on 2019/9/2.
//  Copyright © 2019 Apple_Lance. All rights reserved.
//

#include <iostream>
#include <stdio.h>
long long F[91];

int main(int argc, const char * argv[]) {
    F[1] = 1;
    F[2] = 2;
    for(int i = 3;i <= 90;i++)
        F[i] = F[i - 1] + F[i - 2];
    int n;
    while(scanf("%d", &n) != EOF){
        printf("%lld", F[n]);
    }
    
    return 0;
}

T2

不容易系列之一(九度教程第 94 题) 时间限制:1 秒 内存限制:128 兆 特殊判题:否 题目描述: 大家常常感慨,要做好一件事情真的不容易,确实,失败比成功容易多了! 做好“一件”事情尚且不易,若想永远成功而总从不失败,那更是难上加难了,就像花钱总是比挣钱容易的道理一样。话虽这样说,我还是要告诉大家,要想失败到一定程度也是不容易的。比如,我高中的时候,就有一个神奇的女生,在英语考试的时候,竟然把 40 个单项选择题全部做错了!大家都学过概率论,应该知道出现这种情况的概率, 所以至今我都觉得这是一件神奇的事情。 如果套用一句经典的评语,我们可以这样总结:一个人做错一道选择题并不难,难的是全部做错,一个不对。不幸的是,这种小概率事件又发生了,而且就在我们身边: 事情是这样的——HDU 有个网名叫做 8006 的男性同学, 结交网友无数, 最近该同学玩起了浪漫,同时给 n 个网友每人写了一封信,这都没什么,要命的是,他竟然把所有的信都装错了信封!注意了,是全部装错哟!现在的问题是:请大家帮可怜的 8006 同学计算一下,一共有多少种可能的错误方式呢? 输入: 输入数据包含多个多个测试实例, 每个测试实例占用一行, 每行包含一个正整数 n(1<n<=20) ,n 表示 8006 的网友的人数。
输出: 对于每行输入请输出可能的错误方式的数量,每个实例的输出占用一行。
样例输入: 2 3 样例输出: 1 2

提示

在任意一种错装方案中,假设 n 号信封里装的是 k 号信封的信,而 n 号信封里的信则装在 m 号信封里。按照 k 和 m 的等值与否将总的错装方式分为两类。

  1. 若 k 不等于 m:交换 n 号信封和 m 号信封的信后,n 号信封里装的恰好是对应的信,而 m 号信封中错装 k 号信封里的信,即除 n 号信封外其余 n-1 个信封全部错装,其错装方式等于 F[n - 1],又由于 m 的 n-1 个可能取值,这类错装方式总数为(n - 1)* F[n - 1]。也可以理解为,在 n-1 个信封错装的 F[n - 1]种方式的基础上,将 n 号信封所装的信与 n - 1 个信封中任意一个信封(共有 n-1 中选择)所装的信做交换后,得到所有信封全部错装的方式数。
  2. 另一种情况,若 k 等于 m:交换 n 号信封和 m 号信封的信后,n 号信封和 m 号信封里装的恰好是对应的信,这样除它们之外剩余的 n-2 个信封全部错装,其错装方式为 F[n - 2], 又由于 m 的 n-1 个取值, 这类错装方式总数为 (n - 1) * F[n - 2]。也可以理解为,在 n - 2 个信封全部错装的基础上,交换最后两个信封中的信(n 号信封和 1 到 n-1 号信封中任意一个, 共有 n-1 种选择), 使所有的信封全部错装的方式数。
    综上所述, F [ n ] = ( n 1 ) F [ n 1 ] + ( n 1 ) F [ n 2 ] F[n] = (n - 1) * F[n - 1] + (n - 1) * F[n - 2]
//
//  main.cpp
//  WrongSort
//
//  Created by Apple on 2019/9/2.
//  Copyright © 2019 Apple_Lance. All rights reserved.
//

#include <iostream>
#include <stdio.h>
using namespace std;
long long F[21];

int main(int argc, const char * argv[]) {
    // insert code here...
    F[1] = 0;
    F[2] = 1;
    for(int i = 3; i <= 20;i++)
        F[i] = (i-1) * (F[i - 1] + F[i - 2]);
    int n;
    while(scanf("%d", &n) != EOF)
        printf("%lld", F[n]);
    return 0;
}

二、最长增长子序列(LIS)

拦截导弹
时间限制:1 秒
内存限制:32 兆
特殊判题:否
题目描述:某国为了防御敌国的导弹袭击, 开发出一种导弹拦截系统。 但是这种导弹拦截系统有一个缺陷: 虽然它的第一发炮弹能够到达任意的高度, 但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,并观测到导弹依次飞来的高度,请计算这套系统最多能拦截多少导弹。拦截来袭导弹时,必须按来袭导弹袭击的时间顺序,不允许先拦截后面的导弹,再拦截前面的导弹。

输入: 每组输入有两行, 第一行,输入雷达捕捉到的敌国导弹的数量 k(k<=25) , 第二行,输入 k 个正整数,表示 k 枚导弹的高度,按来袭导弹的袭击时间顺序给出,以空格分隔。

输出: 每组输出只有一行,包含一个整数,表示最多能拦截多少枚导弹。

样例输入:
8
300 207 155 300 299 170 158 65

样例输出:
6

//
//  main.cpp
//  StopMissle
//
//  Created by Apple on 2019/9/2.
//  Copyright © 2019 Apple_Lance. All rights reserved.
//

#include <iostream>
#include <stdio.h>
using namespace std;

int max(int a, int b){return a>b?a:b;}
int list[26];
int dp[26];

int main(int argc, const char * argv[]) {
    int n;
    while(scanf("%d", &n) != EOF){
        for (int i = 1; i <= n; i++) {
            scanf("%d", &list[i]);
        }
        for(int i = 1;i <= n;i++){
            int tmax = 1;
            for(int j = 1;j < i;j++){
                if(list[j] >= list[i])
                    tmax  = max(tmax, dp[j] + 1);
            }
            dp[i] = tmax;
        }
        printf("%d", dp[n]);
    }
    return 0;
}

三、最长公共子序列(LCS)

Coincidence
时间限制:1 秒
内存限制:32 兆
特殊判题:否

题目描述: Find a longest common subsequence of two strings.

输入: First and second line of each input case contain two strings of lowercase character a…z. There are no spaces before, inside or after the strings. Lengths of strings do not exceed 100.

输出: For each case, output k – the length of a longest common subsequence in one line.

样例输入:
abcd
cxbydz

样例输出:
2

//
//  main.cpp
//  LCS
//
//  Created by Apple on 2019/9/2.
//  Copyright © 2019 Apple_Lance. All rights reserved.
//

#include <iostream>
#include <stdio.h>
#include <string>
using namespace  std;

int dp[101][101];
int max(int a, int b){return a>b?a:b;}

int main(int argc, const char * argv[]) {
    char S1[101], S2[101];
    while(scanf("%s%s", S1, S2) != EOF){
        int L1 = strlen(S1);
        int L2 = strlen(S2);
        for(int i = 1;i <= L1;i++)
            dp[i][0] = 0;
        for(int i = 1;i <= L2;i++)
            dp[0][i] = 0;
        for(int i = 1;i <= L1;i++){
            for(int j = 1;j <= L2;j++){
                if(S1[i - 1] == S2[j - 1])
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                else
                    dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);
            }
        }
        printf("%d\n", dp[L1][L2]);
    }
    return 0;
}

分析:状态与状态转移方程

动态规划问题的求解中,相关时间复杂度的估计。
以最长公共子序列为例, 设两个字符串长度分别为 L1 和 L2, 则共有 L 1 L 2 L1*L2 个状态要求解,为了求解每个状态,我们按照相应字符是否相等选取 d p [ i 1 ] [ j 1 ] + 1 dp[i - 1][j - 1] + 1 或者 m a x d p [ i ] [ j 1 ] , d p [ i 1 ] [ j ] d p [ i ] [ j ] max{dp[i][j - 1],dp[i - 1][j]}为 dp[i][j] 的值,即状态转移过程中每个状态的得出仅需要 O ( 1 ) O(1) 的时间复杂度,所以总的时间复杂度为 O ( L 1 L 2 1 ) O(L1 * L2 * 1)
通过以上分析, 同样的我们也可以判断出求解最长递增子序列问题的时间复杂度构成:假设原数列长度为 n,则状态数量为 d p [ n ] dp[n] ,状态转移过程中每个状态的得出复杂度平均为 O ( n ) O(n) ,所以其总的时间复杂度为 O ( n n ) O(n*n)
总结一下, 动态规划问题的时间复杂度由两部分组成: 状态数量和状态转移复杂度,往往程序总的复杂度为它们的乘积。

动态规划问题分析举例

T1

搬教室

题目描述: 搬寝室是很累的,xhd 深有体会.时间追述 2006 年 7 月 9 号,那天 xhd 迫于无奈要从 27 号楼搬到 3 号楼,因为 10 号要封楼了.看着寝室里的 n 件物品,xhd 开始发呆,因为 n 是一个小于 2000 的整数,实在是太多了,于是 xhd 决定随便搬 2k 件过去就行了.但还是会很累,因为 2k 也不小是一个不大于 n 的整数.幸运的是 xhd 根据多年的搬东西的经验发现每搬一次的疲劳度是和左右手的物品的重量差的平方成正比(这里补充一句,xhd 每次搬两件东西,左手一件右手一件).例如 xhd 左手拿重量为 3 的物品,右手拿重量为 6 的物品,则他搬完这次的疲劳度为(6-3)^2 = 9.
现在可怜的 xhd 希望知道搬完这 2*k 件物品后的最佳状态是怎样的(也就是最低的疲劳度),请告诉他吧.

输入: 每组输入数据有两行,第一行有两个数 n,k(2<=2*k<=n<2000).第二行有 n 个整数分别表示 n 件物品的重量(重量是一个小于 2^15 的正整数).

输出: 对应每组输入数据,输出数据只有一个表示他的最少的疲劳度,每个一行.

样例输入:
2 1
1 3

样例输出:
4

//
//  main.cpp
//  Mover
//
//  Created by Apple on 2019/9/2.
//  Copyright © 2019 Apple_Lance. All rights reserved.
//

#include <iostream>
#include <stdio.h>
using namespace std;
#define inf 0x7fffffff

int list[2001];
int dp[1001][2001];


int main(int argc, const char * argv[]) {
    int n, k;
    while(scanf("%d%d", &n, &k) != EOF){
        for(int i = 1;i <= n;i++)
            scanf("%d", &list[i]);
        sort(list + 1, list + n + 1);
        for(int i = 1;i <= n;i++)
            dp[0][i] = 0;
        for(int i = 1;i <= n;i++){
            for(int j = 2*i;j <= n;j++){
                if(j > 2*i)
                    dp[i][j] = dp[i][j-1];
                else
                    dp[i][j] = inf;
                if(dp[i][j] > dp[i][j - 2] + (list[j] - list[j - 1]) * (list[j] - list[j - 1]))
                    dp[i][j] = dp[i][j - 2] + (list[j] - list[j - 1]) * (list[j] - list[j - 1]);
                
            }
        }
        printf("%d", dp[k][n]);
    }
    return 0;
}

T2

Greedy Tino
时间限制:1 秒
内存限制:32 兆
特殊判题:否
题目描述: Tino wrote a long long story. BUT! in Chinese.So I have to tell you the problem directly and discard his long long story. That is tino want to carry some oranges with “Carrying pole”, and he must make two side of the Carrying pole are the same weight.
Each orange have its’ weight. So greedy tino want to know the maximum weight he can carry.

输入: The first line of input contains a number t, which means there are t cases of the test data.for each test case, the first line contain a number n, indicate the number of oranges.the second line contains n numbers, Wi, indicate the weight of each orange.n is between 1 and 100, inclusive. Wi is between 0 and 2000, inclusive. the sum of Wi is equal or less than 2000.

输出: For each test case, output the maximum weight in one side of Carrying pole. If you can’t carry any orange, output -1. Output format is shown in Sample Output.

样例输入:
1
5
1 2 3 4 5

样例输出:
Case 1: 7

//
//  main.cpp
//  GreedyTino
//
//  Created by Apple on 2019/9/2.
//  Copyright © 2019 Apple_Lance. All rights reserved.
//

#include <iostream>
#include <stdio.h>
using namespace std;
#define OFFSET 2000
#define INF 0x7fffffff

int dp[101][4001];
int list[101];

int main(int argc, const char * argv[]) {
    int T;
    int cas = 0;
    scanf("%d", &T);
    while(T--){
        int n;
        scanf("%d", &n);
        bool HaveZero = false;
        int cnt = 0;
        for(int i = 1;i <= n;i++){
            scanf("%d", &list[++cnt]);
            if(list[cnt] == 0){
                cnt--;
                HaveZero = true;
            }
        }
        n = cnt;
        for(int i = -2000;i <= 2000;i++)
            dp[0][i + OFFSET] = -INF;
        dp[0][0+OFFSET] = 0;
        for(int i = 1;i <= n;i++){
            for(int j = -2000;j <= 2000;j++){
                int tmp1 = -INF, tmp2 = -INF;
                if(j + list[i] <= 2000 && dp[i - 1][j + list[i] + OFFSET] != -INF)
                    tmp1 = dp[i - 1][j + list[i] + OFFSET] + list[i];
                if(j + list[i] >= -2000 && dp[i - 1][j - list[i] + OFFSET] != -INF)
                    tmp2 = dp[i - 1][j - list[i] + OFFSET] + list[i];
                if(tmp1 < tmp2)
                    tmp1 = tmp2;
                if(tmp1 < dp[i - 1][j + OFFSET])
                    tmp1 = dp[i - 1][j + OFFSET];
                dp[i][j + OFFSET] = tmp1;
//                if(tmp1 != -INF)
//                    printf("I:%d\tJ:%d\tTMP:%d\n", i, j, tmp1);
            }
        }
            
        printf("case %d:", ++cas);
        if(dp[n][0 + OFFSET] == 0)
            puts(HaveZero?"0":"-1");
        else
            printf("%d\n", dp[n][0 + OFFSET]/2);
    }
    
return 0;
                
}
发布了182 篇原创文章 · 获赞 101 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/lancecrazy/article/details/100191895