更新一下今天 “计算理论与算法设计” 期末考试的三道编程题,没有测试用例的题真是太蓝受了。分享一下我考试时的思路,最后也给出了我编写的自测用例。仅供参考,如有错误还请指出~
01
成绩 15 开启时间 2020年06月24日 星期三 15:10 折扣 0.8 折扣时间 2020年06月24日 星期三 18:30 允许迟交 否 关闭时间 2020年06月24日 星期三 18:30 题目描述:
从前有一群鹰,它们很喜欢玩叠罗汉,于是就叫它们罗汉鹰。每个罗汉鹰都有一定的翅膀宽度和身高,而身高又分为两部分,上半身高度和腿长。每个正在叠罗汉的鹰的上半身高度都会算到整个的高度中,但是只有最下面那个的腿是露出来的并且算入整体高度。现在它们又要开始叠罗汉了。但是由于某些鹰互为好朋友,想待在一起聊天,所以每个鹰只愿意和它们指定的一些鹰相邻(相邻就是指在另一个鹰的上一个或下一个),当然它也可以独自站在那。你的任务就是给你一群罗汉鹰,你要从中选出一些来叠罗汉,使得总高度最高。但是为了美观起见,鹰在叠罗汉的时候,他们翅膀宽度必须严格地从大到小排列,最下面那个最宽。
输入格式:
首先第一行是一个整数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↵
- 2↵
- 5 6 7 1 2↵
- 6 6 8 1 1↵
- 20↵
1秒 64M 0
这道题我是最后一个去做的,一开始瞄了一眼没有思路就先跳过了。最后再来看这道题,用动态规划解决的。过了自己的用例感觉没啥问题~
首先为什么会想到用动态规划呢?当然是此题很明显有重叠子问题的性质啦!(放屁!明明是后两题都没有出现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 数组的第几个)。很容易写出状态转移方程:
其中:且。
应该很好理解,只是要注意 match 数组中记录的可相邻的情况是针对排序之前的顺序的,所以应该将当前第 t 个对应到 id 上。
还有初始条件:
根据状态转移方程,就很容易写出动态规划部分的核心代码:
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,做最扎实的程序员 ----
旨在用心写好每一篇文章,平常会把笔记汇总成推送更新~