UVa Problem 849 Radar Tracking (雷达追踪)

// Radar Tracking (雷达追踪)
// PC/UVa IDs: 111406/849, Popularity: C, Success rate: low Level: 2
// Verdict: Accepted
// Submission Date: 2016-04-06
// UVa Run Time: 0.000s
//
// 版权所有(C)2016,邱秋。metaphysis # yeah dot net
//
// 一道不是非常完善的题目。测试数据对应的输出和评判程序可能有问题,不过好在测试数据应该比较简单,不然很难 AC。截止 AC 时,只有 22 人通过。

#include <iostream>
#include <algorithm>
#include <iomanip>
#include <cmath>

using namespace std;

const double PI = 3.14159265358979323846;
const int COUNTER_CLOCKWISE = 0, CLOCKWISE_SAME_SAME_CYCLE = 1,
    CLOCKWISE_SAME_NEXT_CYCLE = 2, CLOCKWISE_NEXT_NEXT_CYCLE = 3;

struct point
{
    double angle, distance;
};

bool cmp(point x, point y)
{
    return (x.angle != y.angle) ? x.angle < y.angle : x.distance > y.distance;
}

double toRadian(double degree)
{
    return (degree / 180.0) * PI;
}

double toDegree(double radian)
{
    return (radian / PI) * 180.0;
}

// 根据不同情况进行计算。
double getValue(double b, double c, double A, double x, double C, int category)
{
    if (category == COUNTER_CLOCKWISE)
        return b * sin(x) - sin(A - x) * c * (2.0 * PI - x) / (2.0 * PI - C);
    else if (category == CLOCKWISE_SAME_SAME_CYCLE)
        return b * sin(x) - sin(A - x) * c * x / C;
    else if (category == CLOCKWISE_SAME_NEXT_CYCLE)
        return b * sin(x) - sin(A - x) * c * (2.0 * PI + x) / C;
    else if (category == CLOCKWISE_NEXT_NEXT_CYCLE)
        return b * sin(x) - sin(A - x) * c * (2.0 * PI + x) / (2.0 * PI + C);
}

double binarySearch(double b, double c, double A, double C, int category)
{
    // 求取边界值。
    double x = 0.0;
    while (x < A)
    {
        x += (0.01 * PI / 180.0);
        if (getValue(b, c, A, x, C, category) > 0)
            break;
    }

    // 二分查找。
    double up = x - (0.01 * PI / 180.0), down = x, middle;
    while (fabs(up - down) > 1e-10)
    {
        middle = (up + down) / 2.0;
        if (getValue(b, c, A, middle, C, category) < 0)
            up = middle;
        else
            down = middle;
    }

    return middle;
}

void calculate(point first, point second)
{
    point solutions[3];
    int counter = 0;

    // 当前后两次的角度相同时,说明物体的运动轨迹是过极点的直线。
    if (first.angle == second.angle)
    {
        // 远离极点方向。
        if (second.distance >= first.distance)
            solutions[counter++] = (point){first.angle, second.distance + (second.distance - first.distance)};
        // 靠近极点方向,可能会越过极点到相对的另一侧。
        else
        {
            if (second.distance > (first.distance - second.distance))
                solutions[counter++] = (point){first.angle, second.distance - (first.distance - second.distance)};
            else
                solutions[counter++] = (point){
                ((first.angle >= 180.0) ? (first.angle - 180.0) : (first.angle + 180.0)),
                fabs(second.distance - (first.distance - second.distance))};
        }
    }
    // 当前后两次观察到的位置极角相差 180 度时,说明物体的运动轨迹是过极点的直线。
    else if (fabs(first.angle - second.angle) == 180.0)
    {
        // 注意在第二次观察时,时间过去了 1 s,但是再下一次观察时,将是 3s。故移动距
        // 离要增加一倍。有两种可能。
        solutions[counter++] = (point){second.angle, 3 * second.distance + 2 * first.distance};
        if (fabs(2 * second.distance - first.distance) < 1e-8)
            solutions[counter++] = (point){second.angle, first.distance + second.distance};
    }
    // 当第一次和第二次的极角不同,且相差不是 180 度时,物体的运动轨迹是一条不过极点的直线。
    else
    {
        // 当第二次观察位置出现在第一次的逆时针方向,则表明雷达是在一个周期的时间内观察
        // 到两个位置。则第三次观察时只有一种可能的位置。
        double C = 0.0, finalAngle, finalDistance, x;
        double angleDiff = fabs(first.angle - second.angle);
        if ((first.angle > second.angle && angleDiff < 180.0) || (first.angle < second.angle && angleDiff > 180.0))
        {
            // 二分数值法求近似解。
            // 先求出前后两次观察位置之间的角度差,设为角 C,对应边为 c。
            if (first.angle < second.angle && angleDiff > 180.0)
                C = toRadian(first.angle + (360.0 - second.angle));
            else
                C = toRadian(first.angle - second.angle);

            // 不失一般性,设第一次观察的位置距离为 a,第二次观察的距离为 b,可
            // 由余弦定理求出边 a 对应的角 A。先求边 c。
            double a = first.distance, b = second.distance;
            double c = sqrt(a * a + b * b - 2.0 * a * b * cos(C));
            double A = acos((b * b + c * c - a * a) / (2.0 * b * c));

            // 找到边界值。
            double x = A;
            while (x > 0.0)
            {
                x -= (0.01 * PI / 180.0);
                if (getValue(b, c, A, x, C, COUNTER_CLOCKWISE) < 0)
                    break;
            }

            // 二分查找。
            double up = x + (0.01 * PI / 180.0), down = x, middle;
            while (fabs(up - down) > 1e-10)
            {
                middle = (up + down) / 2.0;
                if (getValue(b, c, A, middle, C, COUNTER_CLOCKWISE) > 0)
                    up = middle;
                else
                    down = middle;
            }

            // 计算距离。
            x = middle;
            finalAngle = second.angle - toDegree(x);
            if (finalAngle < 0.0)
                finalAngle += 360.0;
            finalDistance = b * sin(A) / sin(A - x);{finalAngle, finalDistance};
        }
        else
        {
            // 第二个位置在第一个位置的顺时针方向,有两种可能,一种是在观察到第一个位置后的
            // 同一个扫描周期内观察到第二个位置,另外一种情况是在下一个扫描周期内观察到第二
            // 个位置。则有三种情况:第 1,2,3 次观察同时处于一个周期内,第 1,2 次观察处于同一
            // 周期,第3次观察处于第二个周期,第1次观察处于第一个周期,第2次观察处于第二个周期
            // 第三次观察处于第三个周期,如果要 AC,只能输出两组答案,能 AC 的组合是第一种和
            // 第三种情况或者第二种和第三种情况。比较奇怪。
            if (first.angle > second.angle && angleDiff > 180.0)
                C = toRadian(second.angle + (360.0 - first.angle));
            else
                C = toRadian(second.angle - first.angle);

            double a = first.distance, b = second.distance;
            double c = sqrt(a * a + b * b - 2.0 * a * b * cos(C));
            double A = acos((b * b + c * c - a * a) / (2.0 * b * c));

            // 思路同前,二分求值,不过在边界值的求取时有差异,所以未能把前面的二分
            // 查找和此处的二分查找统一成为一个函数。
            double x = binarySearch(b, c, A, C, CLOCKWISE_SAME_SAME_CYCLE);
            double finalAngle = second.angle + toDegree(x);
            if (finalAngle > 360.0)
                finalAngle -= 360.0;
            if (fabs(finalAngle - second.angle) < 0.02)
            {
                x = binarySearch(b, c, A, C, CLOCKWISE_SAME_NEXT_CYCLE);
                finalAngle = second.angle + toDegree(x);
                if (finalAngle > 360.0)
                    finalAngle -= 360.0;
            }
            finalDistance = b * sin(A) / sin(A - x);
            solutions[counter++] = (point){finalAngle, finalDistance};

            x = binarySearch(b, c, A, C, CLOCKWISE_NEXT_NEXT_CYCLE);
            finalAngle = second.angle + toDegree(x);
            if (finalAngle > 360.0)
                finalAngle -= 360.0;
            finalDistance = b * sin(A) / sin(A - x);
            solutions[counter++] = (point){finalAngle, finalDistance};
        }
    }

    sort(solutions, solutions + counter, cmp);

    for (int i = 0, printBlank = false; i < counter; i++)
    {
        if (printBlank)
            cout << " ";
        else
            printBlank = true;

        cout << fixed << setprecision(2) << solutions[i].angle << " ";
        cout << fixed << setprecision(2) << solutions[i].distance;
    }
    cout << endl;
}

int main (int argc, char *argv[])
{
    point first, second;
    while (cin >> first.angle >> first.distance,
            cin >> second.angle >> second.distance)
        calculate(first, second);

    return 0;
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值