区间DP | 罗汉鹰问题

       更新一下今天 “计算理论与算法设计” 期末考试的三道编程题,没有测试用例的题真是太蓝受了。分享一下我考试时的思路,最后也给出了我编写的自测用例。仅供参考,如有错误还请指出~


01

成绩15开启时间2020年06月24日 星期三 15:10
折扣0.8折扣时间2020年06月24日 星期三 18:30
允许迟交关闭时间2020年06月24日 星期三 18:30

题目描述:

从前有一群鹰,它们很喜欢玩叠罗汉,于是就叫它们罗汉鹰。每个罗汉鹰都有一定的翅膀宽度和身高,而身高又分为两部分,上半身高度和腿长。每个正在叠罗汉的鹰的上半身高度都会算到整个的高度中,但是只有最下面那个的腿是露出来的并且算入整体高度。现在它们又要开始叠罗汉了。但是由于某些鹰互为好朋友,想待在一起聊天,所以每个鹰只愿意和它们指定的一些鹰相邻(相邻就是指在另一个鹰的上一个或下一个),当然它也可以独自站在那。你的任务就是给你一群罗汉鹰,你要从中选出一些来叠罗汉,使得总高度最高。但是为了美观起见,鹰在叠罗汉的时候,他们翅膀宽度必须严格地从大到小排列,最下面那个最宽。

01

输入格式:

首先第一行是一个整数T,表示用例组数。

接下来T组用例。每组第一行一个整数n代表给你的鹰数。接下来有n行,第i行首先是3个整数,代表i号鹰的翅膀宽度w,上半身高度h和腿长g。然后有一个数k,代表它愿意相邻的鹰数,之后有k个整数Xj代表它愿意和哪k个鹰相邻。

其中 0 < n <= 500,0 <= w,h,g <= 10000,0 <= k <= n,1 <= Xj<= n。

数据保证任意两个鹰w不同,并且若i愿意和j相邻,那么j必愿意和i相邻。

输出格式:

对每组用例,输出一行一个整数,代表能达到的最大高度。

 

 测试输入期待的输出时间限制内存限制额外进程
测试用例 1 
  1. 1↵
  2. 2↵
  3. 5 6 7 1 2↵
  4. 6 6 8 1 1↵
 
  1. 20↵
1秒64M0

       这道题我是最后一个去做的,一开始瞄了一眼没有思路就先跳过了。最后再来看这道题,用动态规划解决的。过了自己的用例感觉没啥问题~

       首先为什么会想到用动态规划呢?当然是此题很明显有重叠子问题的性质啦!放屁!明明是后两题都没有出现dp所以这道题就试一试dp尝试思考如何根据子问题推到另一个问题的解,并且如何表达动规变量?当时想了二十多分钟吧,为了避免凌乱,就不具体说我是咋想到的了,下面直接摆上我的思路:



1、数据预处理

       首先这道题的数据比较复杂,简单来看,我们需要储存:每一个罗汉鹰🦅的宽 weight、高 height、腿长 leg 以及可以相邻的其他鹰。其实有一个潜在的数据也需要储存,即标识每一只鹰的编号,我们记为 id。为了方便处理,对每一只罗汉鹰用结构体来表示,然后定义一个罗汉鹰地结构体数组:

(emmmmm至于鹰为啥用了 "bird",实在是考试的时候想不起鹰的英文了,我觉得还是比用 “ying” 专业一点)

struct node {
    int id;  //最开始鹰的输入顺序作为 id
    int width, height, leg; 
    bool match[MAXN];   //match[i] = true表示此鹰可以与id=i的鹰相邻
} bird[MAXN];

        接着处理输入,我把输入的处理写在一个 Init() 函数中。并且考虑到在后面寻找堆积结果的时候,第一个应该要考虑的就是宽度递减地堆积,所以说我们应该会需要对宽度进行排序,让罗汉鹰以 width 递减的顺序储存在 bird[ ] 中。对结构体以特定的元素排序,可以直接采用 algorithm 库中的 sort 函数,具体使用方式参考这一篇文章:排序算法 | sort函数的使用。主要注意本题 bird 数组我是从下标1开始使用的。

bool cmp(struct node x, struct node y) {
    return x.width > y.width;
}

void Init() {
    scanf("%d", &n);
    memset(dp, 0, sizeof(dp));   //初始化!

    for (int i = 1; i <= n; i++) {
        bird[i].id = i;
        memset(bird[i].match, false, sizeof(bird[i].match));  //初始化
        
        scanf("%d %d %d", &bird[i].width, &bird[i].height, &bird[i].leg);
        int k, t;
        scanf("%d", &k);
        for (int j = 0; j < k; j++) {
            scanf("%d", &t);
            bird[i].match[t] = true;
        }
    }
    sort(bird + 1, bird + 1 + n, cmp);   //对bird数组以width为关键字排序
}

2、动规思路

       已经将所有的输入处理好,且按照 width 递减的顺序储存在 bird 数组中。由于相邻的鹰要考虑是否能在一起,所以对于子问题中
:最下面的鹰和最上面的鹰是谁是至关重要的。那么记 dp[ i ][ j ] :以第 i 只鹰作为最底端,第 j 只鹰作为最上端的罗汉层高度。(这里的第几只表示的是排序后 bird 数组的第几个)。很容易写出状态转移方程:

dp[i][j+1] = max(dp[i][t] + bird[j+1].height)  

其中:i\leq t\leq jbird[j].match[bird[t].id] == true

        应该很好理解,只是要注意 match 数组中记录的可相邻的情况是针对排序之前的顺序的,所以应该将当前第 t 个对应到 id 上。

还有初始条件:dp[i][i]=bird[i].leg + bird[i].height

       根据状态转移方程,就很容易写出动态规划部分的核心代码:

        int ans = 0;  //最终的最高高度
        /* 依次考虑每一只鹰作为最底端 */
        for (int i = 1; i <= n; i++) {
            dp[i][i] = bird[i].leg + bird[i].height;  //初始化
            ans = max(ans, dp[i][i]);
            /* 依次考虑i后每一只鹰作为最顶端 */
            for (int j = i + 1; j <= n; j++) {
                dp[i][j] = 0;  //初始化
                /* 在子问题中找到最优的匹配 */
                for (int t = i; t <= j - 1; t++) {
                    if (bird[j].match[bird[t].id])   //合适的才可以结合
                        dp[i][j] = max(dp[i][j], dp[i][t] + bird[j].height);  
                }
                ans = max(ans, dp[i][j]);
            }
        }
        printf("%d\n", ans);


下面附上完整代码测试用例

#include <cstdio>
#include <cstring>
#include <algorithm>

#define MAXN 550
using namespace std;

int n;
int dp[MAXN][MAXN] = {0};

struct node {
    int id;  //最开始鹰的输入顺序作为 id
    int width, height, leg;
    bool match[MAXN];   //match[i] = true表示此鹰可以与id=i的鹰相邻
} bird[MAXN];

bool cmp(struct node x, struct node y) {
    return x.width > y.width;
}

void Init() {
    scanf("%d", &n);
    memset(dp, 0, sizeof(dp));   //初始化!

    for (int i = 1; i <= n; i++) {
        bird[i].id = i;
        memset(bird[i].match, false, sizeof(bird[i].match));  //初始化

        scanf("%d %d %d", &bird[i].width, &bird[i].height, &bird[i].leg);
        int k, t;
        scanf("%d", &k);
        for (int j = 0; j < k; j++) {
            scanf("%d", &t);
            bird[i].match[t] = true;
        }
    }
    sort(bird + 1, bird + 1 + n, cmp);   //对bird数组以width为关键字排序
}

int main() {
    int t;

    for (scanf("%d", &t); t; t--) {
        Init();
        int ans = 0;  //最终的最高高度
        /* 依次考虑每一只鹰作为最底端 */
        for (int i = 1; i <= n; i++) {
            dp[i][i] = bird[i].leg + bird[i].height;  //初始化
            ans = max(ans, dp[i][i]);
            /* 依次考虑i后每一只鹰作为最顶端 */
            for (int j = i + 1; j <= n; j++) {
                dp[i][j] = 0;  //初始化
                /* 在子问题中找到最优的匹配 */
                for (int t = i; t <= j - 1; t++) {
                    if (bird[j].match[bird[t].id])   //合适的才可以结合
                        dp[i][j] = max(dp[i][j], dp[i][t] + bird[j].height);
                }
                ans = max(ans, dp[i][j]);
            }
        }
        printf("%d\n", ans);
    }

}

测试用例(5组)

输入:

5
2
5 6 7 1 2
6 6 8 1 1
5
2 3 5 2 4 5
3 5 6 1 3
4 5 6 1 2
5 7 10 2 1 5
6 3 2 2 1 4
1
100 4 6 0
5
2 3 5 3 3 4 5
3 5 6 1 3
4 5 20 2 1 2
5 7 10 2 5 1
6 3 2 2 4 1
2
10 2 7 0
11 3 9 0


正确输出:

20

20

10

30

12



end 

欢迎关注个人公众号 鸡翅编程 ”,这里是认真且乖巧的码农一枚。

---- 做最乖巧的博客er,做最扎实的程序员 ----

旨在用心写好每一篇文章,平常会把笔记汇总成推送更新~

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值