@[TOC]([蓝桥杯] 荒岛探测 (扫描线 \ 积分))
时间限制:1.0s 内存限制:256.0MB
科学家小蓝来到了一个荒岛,准备对这个荒岛进行探测考察。
小蓝使用了一个超声定位设备来对自己进行定位。为了使用这个设备,小蓝需要在不同的点分别安装一个固定的发射器和一个固定的接收器。小蓝手中还有一个移动设备。定位设备需要从发射器发射一个信号到移动设备,移动设备收到后马上转发,最后由接收器接收,根据这些设备之间传递的时间差就能计算出移动设备距离发射器和接收器的两个距离,从而实现定位。
小蓝在两个位置已经安装了发射器和接收器,其中发射器安装在坐标 ,接收器安装在坐标 。小蓝的发射器和接收器可能在岛上,也可能不在岛上。
小蓝的定位设备设计有些缺陷,当发射器到移动设备的距离加上移动设备到接收器的距离之和大于 时,定位设备工作不正常。当和小于等于 时,定位设备工作正常。为了安全,小蓝只在定位设备工作正常的区域探测考察。
已知荒岛是一个三角形,三个顶点的坐标分别为 , , 。
请计算,小蓝在荒岛上可以探测到的面积有多大?
输入的第一行包含五个整数,分别为 , , , , 。
第二行包含六个整数,分别为 , , , , , 。
输出一行,包含一个实数,四舍五入保留 位小数,表示答案。
考虑到计算中的误差,只要你的输出与参考输出相差不超过 即可得分。
10 6 4 12 12
0 2 13 2 13 15
39.99
荒岛的形状和定位设备工作正常的区域如下图所示,蓝色的三角形表示荒岛,红色的曲线围成的区域为定位设备工作正常的区域。
当输出为 39.98、39.99 或 40.00 时可以得分。
对于所有评测用例,保证发射器的两个坐标不同,x_A, y_A, x_B, y_B, L在区间[-1000,1000] 内。
本问题是求不规则物体\平面的面积\体积, 我想到的是利用积分的思想解题 。(我真的是谢谢你了,帮我复习高数几何积分,折磨了我一天,欧系给)
一开始的的代码思路是先把写在纸上写个联立方程组求交点的式子化解成一般关系式给写入程序求交,结果发现椭圆的一般方程联立后有点复杂, 以至于我的渣数学功底无力化解为一阶一项的方程,然后动了歪主意,借助从计算机图形学了解到的两种不同处理本地坐标到世界坐标的矩阵旋转思路(转点和转轴),把各点坐标转为当椭圆以原点对称的正椭圆时的值。
然后, 试图让程序带入交点值的坐标开始积分,结果想了会,发现被示例误导了,并不一定交点就是积分始末,它可以是很多种奇奇怪怪的形状,如图所示,或者直接没有交点,被另一个完全包含。
于是我按椭圆两远点和三角形各点x轴值大小来判断该从哪儿开始积分(即要判断被两个图像包含的部分,也就是要求的那部分面积)。
也专门写了个结构体来存线的函数各种参数,和三角形在该直线哪一侧的记录值。(很像高中题型线性规划常见的图样,让我回忆起高中被数学折磨的时光)
然后值得一提的是,正好样例输入旋转后是特殊情况:三角形某边的斜率可能不存在的边界问题,不然可能就死活想不到了。(这个边界小细节值得以后多注意)
最后是d的选择,由于我们人在数学理论上的积分是通过公式的转化和真无穷小的定理完成的,程序无法用短代码实现,只能在一定精度下模拟结果,这对性能和时间就存在一定的要求,积分的无穷小积分分量d就有了给定不同值的意义。(莫名有机器学习深度学习的学习率那味儿了)这里我们需要根据题目精度要求为输出误差不大于±0.01的结果,即d需要适量的小的值(我测得d小于0.002基本就足够满分了, 具体积分误差分析可能得我去复习复习高数才能找到, 这里d过大的时候主要是系统给出小数据的时候有0.0几的误差, 积分嘛), 写入程序对应积分部分, 这个算法问题的代码答案就算大功告成了。
下图为代码示例(有很多地方是可以优化的,但是想了一天一夜我人已经麻了,大概率这几天不会再去改它了)
(注:为了方便调试和查看逻辑问题, 我调用了easyx这个图形库,把对应库和函数删除即可提交测试, 虽然一直在diss这个easyx纹理贴图功能做的屎,但是快速简易地绘制个边框图形还是好用的, easyx对不起 ! 砰\ ORZ)
#include
#include
#include "graphics.h" // 引用图形库头文件
#include
using namespace std;
#define UNUSEED 5000 // 大于区间大小的任意值均可,表示未初值化或异常值
#define PI 3.14159265
#define min(a,b) ((a>b)?b:a)
#define max(a,b) ((a>b)?a:b)
typedef struct fun {
double k; // 斜率
double b; // y轴截距
bool if_bigger_y; // 三角形位于直线的哪个方向(对于有斜率的直线就是在是否直线上方)
bool if_v; // 是否无斜率
}fun;
fun gen_line(double x1, double y1, double x2, double y2, double extr_x, double extr_y)// 初始化直线方程
{
double temp;
if (x1 > x2)
{
temp = x1;
x1 = x2;
x2 = temp;
temp = y1;
y1 = y2;
y2 = temp;
}
fun a;
if (abs(x1 - x2) >= 0.00001)
{
a.if_v = false;
a.k = (y1 - y2) / (x1 - x2);
a.b = -((y1 - y2) / (x1 - x2)) * x1 + y1;
a.if_bigger_y = (a.k * extr_x) + a.b > extr_y ? false : true;
}
else
{
a.if_v = true;
a.b = x1;
a.if_bigger_y = a.b > extr_x ? false : true;
}
return a;
}
double get_line_y(fun triangle[3], int line_num, double x)// 返回直线在x处的y值,对无斜率直线需要联立其他直线来求交点
{
double temp = UNUSEED;
if (!triangle[line_num].if_v) // 不为无斜率直线上时
{
temp = triangle[line_num].k * x + triangle[line_num].b;
}
else if (triangle[line_num].b == x) // 在无斜率直线的x值上时
{
for (size_t j = 0; j < 3; j++)
{
if (line_num == j)
{
continue;
}
else if (temp == UNUSEED)
{
temp = triangle[line_num].b * triangle[j].k + triangle[j].b;
}
else
{
temp = temp > triangle[line_num].b * triangle[j].k + triangle[j].b ? temp : triangle[line_num].b * triangle[j].k + triangle[j].b;
}
}
}
return temp; //如果上两者均不满足,返回未初始化值表示异常
}
bool if_in_tri(fun triangle[3], double x, double y)// 判断是否在三角形内,在返回真,不在返回假
{
for (size_t i = 0; i < 3; i++)
{
double temp = get_line_y(triangle, i, x);
if ((temp == UNUSEED && ((triangle[i].b > x && triangle[i].if_bigger_y) || (triangle[i].b < x && !triangle[i].if_bigger_y)))
|| ((temp != UNUSEED) && ((temp > y && triangle[i].if_bigger_y) || (temp < y && !triangle[i].if_bigger_y))))
// 情况一:三角形存在无斜率直线,且需要判断的点不在上面,根据b,是否在直线左侧if_bigger_y,和点坐标判断
// 情况二:三角形有大于\小于坐标y值的点,且他们是三角形在此x值时的最低点\最高点
{
return false;
}
}
return true;
}
double get_tri_top(fun triangle[3], double x) // 求x处的三角形最大值
{
double max_y = UNUSEED;
for (size_t i = 0; i < 3; i++)
{
double temp = get_line_y(triangle, i, x);
if (if_in_tri(triangle, x, temp) && (max_y == UNUSEED || max_y < temp)) // 在三角形上且比上一个满足条件的值更大
{
max_y = temp;
}
}
return max_y;
}
double get_tri_buttom(fun triangle[3], double x) //求最小值
{
double min_y = UNUSEED;
for (size_t i = 0; i < 3; i++)
{
double temp = get_line_y(triangle, i, x);
if (if_in_tri(triangle, x, temp) &&(min_y == UNUSEED || min_y > temp))
{
min_y = temp;
}
}
return min_y;
}
double get_ellipse_y(double a, double b, double x) //求x处椭圆的正值(是求平方根的原因,只有正值y,返回后单独正负号处理
{
return sqrt((1 - ((x * x) / (a * a))) * b * b);
}
void draw(double a, double b, double xa, double ya, double xb, double yb, double xc, double yc, int scrole = 20) {
initgraph(640, 480); // 创建绘图窗口,大小为 640x480 像素
setorigin(320, 240); // 设置坐标轴位置
setaspectratio(1, -1); // easyx的y轴默认向下为正,-1反转等比缩放
POINT points[4];
points[0].x = xa * scrole;
points[0].y = ya * scrole;
points[1].x = xb * scrole;
points[1].y = yb * scrole;
points[2].x = xc * scrole;
points[2].y = yc * scrole;
points[3].x = xa * scrole;
points[3].y = ya * scrole;
polyline(points, 4);
POINT xy_points[2];
xy_points[0].x = -320;
xy_points[0].y = 0;
xy_points[1].x = 320;
xy_points[1].y = 0;
polyline(xy_points, 2);
xy_points[0].x = 0;
xy_points[0].y = -240;
xy_points[1].x = 0;
xy_points[1].y = 240;
polyline(xy_points, 2);
ellipse(-a * scrole, b * scrole, a * scrole, -b * scrole);
}
void draw_lines(double x, double y1, double y2, double scrole) {
POINT points[2];
points[0].x = x * scrole;
points[0].y = y1 * scrole;
points[1].x = x * scrole;
points[1].y = y2 * scrole;
polyline(points, 2);
}
int main()
{
int scrole = 2; // easyx的图形缩放参数
double x1, y1, x2, y2, L; // 椭圆数据
double xa, ya, xb, yb, xc, yc; // 三角形顶点数据
cin >> x1 >> y1 >> x2 >> y2 >> L;
cin >> xa >> ya >> xb >> yb >> xc >> yc;
double a, b, c, mx, my, theta, dx, dy; // 处理得到椭圆标准方程的各参数:长半轴,焦距等
a = L / 2.0f;
c = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)) / 2;
b = sqrt(a * a - c * c);
//draw(a, b, xa, ya, xb, yb, xc, yc, scrole);// 旋转前的三角形绘制
// 旋转转换新坐标系(三角形顶点存放可以改为数组,用循环减少代码量)
mx = (x1 + x2) / 2.0f;
my = (y1 + y2) / 2.0f;
dx = x1 - x2;
dy = y1 - y2;
theta = -atan2(dy, dx);// 得到椭圆长轴和现坐标系的夹角
// 旋转各点, 依照点到椭圆中心的距离乘以cos\sin来得到新的x\y值
dx = xa - mx;
dy = ya - my;
ya = sin(theta + atan2(dy, dx)) * sqrt(pow(dy, 2) + pow(dx, 2));
xa = cos(theta + atan2(dy, dx)) * sqrt(pow(dy, 2) + pow(dx, 2));
dx = xb - mx;
dy = yb - my;
yb = sin(theta + atan2(dy, dx)) * sqrt(pow(dy, 2) + pow(dx, 2));
xb = cos(theta + atan2(dy, dx)) * sqrt(pow(dy, 2) + pow(dx, 2));
dx = xc - mx;
dy = yc - my;
yc = sin(theta + atan2(dy, dx)) * sqrt(pow(dy, 2) + pow(dx, 2));
xc = cos(theta + atan2(dy, dx)) * sqrt(pow(dy, 2) + pow(dx, 2));
// 把椭圆转移到新的坐标系
x2 = c;
x1 = -x2;
y1 = y2 = 0;
fun lines[3];// 初值化三角形的直线方程组
lines[0] = gen_line(xa, ya, xb, yb, xc, yc);
lines[1] = gen_line(xa, ya, xc, yc, xb, yb);
lines[2] = gen_line(xb, yb, xc, yc, xa, ya);
double min_x = max(-a, min(xa, min(xb, xc)));
double max_x = min(a, max(xa, max(xb, xc)));
double integral = 0.0f;// 积分和
double top, buttom, d = 0.002;
draw(a, b, xa, ya, xb, yb, xc, yc, scrole);// 旋转后的整体绘制
for (double i = min_x; i < max_x; i += d)
{
if (i != min_x && get_ellipse_y(a, b, i) == 0)
{
break;
}
if (i > 41.7)
{
i = i;
}
top = min((get_ellipse_y(a, b, i)), (get_tri_top(lines, i)));
buttom = max((-get_ellipse_y(a, b, i)), (get_tri_buttom(lines, i)));
if (top - buttom > 0.0)
{
// draw_lines(i, top, buttom, scrole);// 每次积分的片元的绘制(简化为绘制线条)
integral += (top - buttom) * d;
}
}
printf("%.2f", integral); // 输出
_getch(); // easyx为显示画面做出的暂停,等待输入一个按键即可退出程序
return 0;
}
下面是一些运行测试的截图
不是, 你看这目标输出和结构输出, 有什么问题吗? 蓝桥杯!你在干什么啊蓝桥杯!甘凌娘!,就是不想给我满分是吧?
最后, 不得不感叹py的代码量是真的少写完博客去搜的python版答案