ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

【课程·研】软件工程 | 结对编程:建造金字塔(1157)

2021-02-16 13:31:34  阅读:218  来源: 互联网

标签:结对 int 1157 建造 软件工程 顶点 金字塔 收益 dp


关于 拾年之璐

微信公众号知行校园汇,点击查看,欢迎关注

其他平台(点击蓝字可访问):

GitHub  |  Gitee  |  哔哩哔哩  |  语雀  |  简书  |  微信小程序  |  知行达摩院  

本文专栏:研究生课程  点击查看系列文章

本文主要内容:

1、实验要求

练习结对编程**(pair programming)**,体验敏捷开发中的两人合作;两人一组,自由组合;使用一台计算机,共同编码,完成实验要求;

在工作期间,两人的角色至少切换4次;使用VS2017和**C++**编程。

2、待求解问题描述与数学模型

时间限制:4000ms

单点时限:2000ms

内存限制:256MB

问题描述:

在二次元中,金字塔是一个底边在x轴上的等腰直角三角形。

你是二次元世界的一个建筑承包商。现在有N个建造订单,每个订单有一个收益w,即建造此金字塔可获得w的收益。对每个订单可以选择建造或不建造。

建造一个金字塔的成本是金字塔的面积,如果两个或多个金字塔有重叠面积,则建造这些金字塔时重叠部份仅需建造一次。

建造一组金字塔的总利润是收益总和扣除成本。现给出这些订单,请求出最大利润。

输入:

输入数据第一行为一个整数T,表示数据组数。

每组数据第一行为一个整数N,表示订单数目。

接下来N行,每行三个整数x, y, w,表示一个订单。(x, y)表示建造出的金字塔的顶点,w表示收益。

输出:

对于每组数据输出一行"Case #X: Y",X表示数据编号(从1开始),Y表示最大利润,四舍五入到小数点后两位。

数据范围:

  • 1 ≤ T ≤ 20

  • 0 ≤ w ≤ 107

小数据

  • 1 ≤ N ≤ 20

  • 0 ≤ x, y ≤ 20

大数据

  • 1 ≤ N ≤ 1000

  • 0 ≤ x, y ≤ 1000

3、算法与数据结构设计

3.1 设计思路

通过对问题描述进行详细的分析,我们发现本题需要解决的关键问题是求解最大收益问题。本题的金字塔建造与否,是可选项,因为有可能发生收益小于成本的情况。同时,重叠的面积的成本,只需要计算一次,再次增加了题目的难度。不过,本题的最终输出结果,只有一个最大利润数值。

下面,我们结合这个题目,进行了解决方案的思考和设计。

  1. 暴力法解决问题

所谓暴力法,就是枚举所有可能发生的情况,并对这些情况逐一求解,然后取这些解的最大值。在本题中,使用暴力法枚举所有的金字塔的排列组合情况是可行的。然后对每一个组合都求解其利润,最后再遍历所有解,取最大值。如果有n座金字塔,那么排列组合情况就有image-20210216130514024种。如果n的数值成百上千时,排列组合的组数就会非常多,每一组内的求解,也需要计算成本、排除重复面积,这使得计算量非常大。所以采用暴力法,虽然有求解的可能性,但在实际的项目中,实现该方法并不切实际,而且容易造成算法的遗漏或者冗余。

  1. 换个角度考虑该题

我们又对该题进行进一步的分析,发现每个金字塔的建造与否,取决于两个因素。第一是金字塔的面积(成本)与收益之间的关系,第二是当前金字塔与其他金字塔重叠面积之间的关系。第一个因素虽然看起来对金字塔建造与否起到决定性因素(或者理解为收益小于成本时,就放弃该金字塔的建造),但这是错误的。因为即使单个金字塔的收益是负数,也会发生该金字塔和周围金字塔面积重叠后,共同的建造成本降低,导致最终收益大于成本。所以,在这个问题中,每个金字塔的建造与否,是一个逐层推进的过程。

  1. 动态规划算法解决问题

动态规划算法(Dynamic Programming,DP)通常用于求解具有某种最优性质的问题。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。在本题中,每个金字塔的建造与否,都可以作为一个子问题来进行分析,同时每个金字塔的建造与否,都与其周围的金字塔有密切的关系,都需要用到周围金字塔的数据。

所以,综合分析后,我们最终拟采用动态规划算法来解决本题。

首先,系统输入的每座金字塔数据,是金字塔的顶点坐标和收益。但是该金字塔的顶点是等腰直角三角形的直角顶点,该顶点不在x周上,不方便判断金字塔与金字塔之间的位置关系,所以需要将其转化成左顶点l、右顶点r,和权重w。转化后,我们的关注点将集中在右顶点r上。

然后,对输入的n个三角形,需要进行一次排序,即按照三角形的左顶点从小到大排序,同时记录所有右顶点的最大值maxr。此外,还需要定义一个数组dp[j],并初始化为无穷小,用来表示区间[0,j]上的最大收益。这里j的变化范围是[0,maxr]。

接下来,需要对每个金字塔x[i]执行一次循环。对于每次循环,需要计算出从第1个金字塔到当前第i个金字塔的最大收益。

最后,在每次循环中,需要对每个坐标(0,j)处的最大收益dp[j]进行更新。这里更新采用倒序更新,即j的取值,从maxr到0。关于如何更新,这里主要有三种情况,或者说是三种状态转移方程:

(1) 当j大于等于x[i]的右顶点坐标r时,记为 ,说明当前金字塔全部包含在[0,j]之间,所以只需要对建造该金字塔和不建造该金字塔的收益中,取较大者。即为:

dp[j]=max⁡{dp[j]+0 ,dp[j]+x[i].w}

(2) 当 x[i].r>j≥x[i].l 时,此时金字塔的局部在 [0,j] 内,这时我们就不关心j位置,而是关系该金字塔的右顶点处( x[i].r )的最大净收益。对于 dp[x[i].r] 位置的最大净收益,是建造当前金字塔的收益w减去多建设的面积,即 w-[S(x[i].l ,x[i].r)-S(x[i].l ,j)] ,所以该阶段的状态转移方程为:

dp[x[i].r]=max⁡{dp[x[i].r] ,dp[j]+w-[S(x[i].l ,x[i].r)-S(x[i].l ,j)]}

(3) 当 j<x[i].l 时,其状态转移方程和上面类似,只是这里的 S(x[i].l ,j) 不存在,故状态转移方程为:

dp[x[i].r]=max⁡{dp[x[i].r] ,dp[j]+w-S(x[i].l ,x[i].r)}

(4) 上述三个条件是考虑了和周围的金字塔的相关性,接下来需要加上只有建造当前金字塔时的净收益,和前面计算的净收益,取较大者。所以这里的状态转移方程为:

dp[x[i].r]=max⁡{dp[x[i].r] ,w-S(x[i].l ,x[i].r)}

当上述过程执行完毕后,dp[j]中的所有数据,表示的即为[0,j]的所有收益,也就是所有区间的收益。然后对这所有的区间遍历,找到收益的最大值,即为题目的最终答案。

3.2 算法流程图

image-20210216131034930

3.3 核心数据结构

本题目用到的核心数据结构主要有两个。

一个是表示三角形的左边界、右边界和收益的结构体,其定义如下:

struct tri {
	//每个三角形的左边界,右边界,收益
	int l, r, w;
}x[1100];

另一个是记录从0到j范围内的最大收益的数字:

double dp[2100];

根据题目给出的数据范围得知,金字塔(三角形)的个数最多为1000,故三角形的个数x取值到1100。金字塔顶点的横坐标最大为1000,表示到坐标轴上,右顶点(边界)横坐标的最大值为2000,故记录每个横坐标节点收益的数组取值到2100。

3.4 时间复杂度分析

在本题中,时间复杂度主要集中在逐个三角形求解的双重循环中,所以其时间复杂度为O(n2)。

4、实现

4.1 编程语言与环境

  • C/C++语言

  • Microsoft Visio Studio 2017

4.2 完整源码

// 建造金字塔.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<algorithm> //max函数,sort函数
#include<string>
#include<sstream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;

//dp[j]表示[0,j]范围内的最大收益
double dp[2100];

struct tri {
	//每个三角形的左边界,右边界,收益
	int l, r, w;
}x[1100];

int n;//订单数目

//比较,按照左边界由小到大排序
int compare(tri k1, tri k2) {
	return k1.l < k2.l;
}

//长度为k的底边的三角形面积
double Area(int k) {
	return k * k / 4.0;
}

//求解
double solve()
{
	//输入订单数目
	cin >> n;

	//maxr表示最大的右边界
	int maxr = 0;

	//输入n组数据
	for (int i = 1; i <= n; i++) {
		int k1, k2, k3;
		//分别表示:顶点横坐标,顶点纵坐标,收益
		cin >> k1 >> k2 >> k3;

		//将输入的数据,保存至结构体数组中
		x[i] = tri{ k1 - k2, k1 + k2, k3 };

		//找出最大右边界
		maxr = max(maxr, k1 + k2);
	}

	//按照左边界由小到大排序
	sort(x + 1, x + n + 1, compare);

	//初始化每个点的最大收益为无穷小
	for (int i = 0; i <= maxr; i++)
		dp[i] = -1e18;

	//求到每个三角形处的最大收益,
	for (int i = 1; i <= n; i++)
	{

		for (int j = maxr; j >= 0; j--) {
			//当j大于等于右边界时候,取建设i金字塔和不建设的最大值
			if (j >= x[i].r)
				dp[j] = max(dp[j], dp[j] + x[i].w);

			//计算净成本,然后w减去净成本就是多出来的收益
			else if (j >= x[i].l)
				dp[x[i].r] = max(dp[x[i].r], dp[j] - Area(x[i].r - x[i].l) + Area(j - x[i].l) + x[i].w);

			//多出来的收益就是第i个金字塔的收益w减去它的面积
			else
				dp[x[i].r] = max(dp[x[i].r], dp[j] - Area(x[i].r - x[i].l) + x[i].w);
		}

		//最后再取只建造自己时候的较大者
		dp[x[i].r] = max(dp[x[i].r], x[i].w - Area(x[i].r - x[i].l));
	}

	double maxdp = 0;
	for (int i = 0; i <= maxr; i++)
		maxdp = max(maxdp, dp[i]);//取所有范围中的最大者
	return maxdp;
}

int main() {
	//freopen("t.txt", "r", stdin);
	int t;
	scanf("%d", &t); //数据的组数
	for (int i = 1; i <= t; i++) {
		printf("Case #%d: %.2lf\n", i, solve());
	}
	return 0;
}

5、结对编程

5.1 分组依据

5.2 角色切换与任务分工

5.3 工作照片

5.4 工作日志

5.5 计划与实际进度

6、总结

以上。

标签:结对,int,1157,建造,软件工程,顶点,金字塔,收益,dp
来源: https://blog.csdn.net/cxh_1231/article/details/113824030

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有