L3-012 水果忍者 (30 分) 动态图解枚举过程

        2010年风靡全球的“水果忍者”游戏,想必大家肯定都玩过吧?(没玩过也没关系啦~)在游戏当中,画面里会随机地弹射出一系列的水果与炸弹,玩家尽可能砍掉所有的水果而避免砍中炸弹,就可以完成游戏规定的任务。如果玩家可以一刀砍下画面当中一连串的水果,则会有额外的奖励,如图1所示。

        现在假如你是“水果忍者”游戏的玩家,你要做的一件事情就是,将画面当中的水果一刀砍下。这个问题看上去有些复杂,让我们把问题简化一些。我们将游戏世界想象成一个二维的平面。游戏当中的每个水果被简化成一条一条的垂直于水平线的竖直线段。而一刀砍下我们也仅考虑成能否找到一条直线,使之可以穿过所有代表水果的线段。

        如图2所示,其中绿色的垂直线段表示的就是一个一个的水果;灰色的虚线即表示穿过所有线段的某一条直线。可以从上图当中看出,对于这样一组线段的排列,我们是可以找到一刀切开所有水果的方案的。

另外,我们约定,如果某条直线恰好穿过了线段的端点也表示它砍中了这个线段所表示的水果。假如你是这样一个功能的开发者,你要如何来找到一条穿过它们的直线呢?

输入格式:

        输入在第一行给出一个正整数N(≤10^{4}​​),表示水果的个数。随后N行,每行给出三个整数x、y​1​​、y​2​​,其间以空格分隔,表示一条端点为(x,y​1​​)和(x,y​2​​)的水果,其中y​1​​>y​2​​。注意:给出的水果输入集合一定存在一条可以将其全部穿过的直线,不需考虑不存在的情况。坐标为区间 [−10^{6}​​,10^{6}) 内的整数。

输出格式:

        在一行中输出穿过所有线段的直线上具有整数坐标的任意两点p​1​​(x​1​​,y​1​​)和p​2​​(x​2​​,y​2​​),格式为 x​1​​y​1​​x​2​​y​2​​。注意:本题答案不唯一,由特殊裁判程序判定,但一定存在四个坐标全是整数的解。

输入样例:

5
-30 -52 -84
38 22 -49
-99 -22 -99
48 59 -18
-36 -50 -72

输出样例:

-99 -99 -30 -52

 思路:

        既然说一定存在一条直线,使它可以穿过所有的线段,那么总有一个解空间的范围吧?

        求解问题时,求边界值比求某一个没有特征的值要简单得多,那么这里的边界值是什么呢?

        要穿过所有的水果,边界值就是如果稍微偏一点点,就有一个水果切不上了,于是乎我们可以认为边界值就是某两个线段的端点所连起来的一条直线上。

        我们将以每一个线段的下端点作为分割线的必经点,再枚举每一个线段的上下端点,求出大斜率小斜率,并且在枚举的过程中不断地缩小最大斜率最小斜率的范围以满足所有大斜率与小斜率的范围。

        下图展示了我们枚举的过程,其中红色线条表示大斜率,蓝色线条表示小斜率,max slope表示最大斜率,min slope表示最小斜率。

        在图的最后,因为最小斜率反而比当前的大斜率还要大,就说明从第一个线段切,在满足第二、第三、第四个线段都被切且经过第一个线段的下断点的前提下,是不可能切到第五个线段的,所以停止枚举。

        这里的终止条件可能不太直观,因此我们将之转化为一维轴,更方便理解一些:

        如果最小斜率大于大斜率 或者 最大斜率小于小斜率,就说明两个集合没有交集,也就说明为了满足解在最小斜率与最大斜率的范围内找一个又满足小斜率与大斜率的范围内的交集是不可能的!

         于是我们继续枚举第二个线段的下端点作为必经点

        注意:第一个线段与第二个线段相连时,我们认为第一个线段上端点与第二个线段的连线时小斜率,反之亦然。这是方便我们后面进行比较。

        第二与第三相连时产生的大斜率与小斜率明显不符合要求,因为小斜率比最大斜率还大了。

 

        我们展示枚举第三条线段的过程,请注意蓝色线段与红色线段的上下位置(蓝色表示小斜率,红色表示大斜率)

        直到最后,筛选出了满足所有大小斜率范围的最大最小区间,那么任意取一最大最小斜率,可作为本题目的解之一。

 参考代码:

#include<bits/stdc++.h>
using namespace std;

struct Line {
    double x, y1, y2;
};

bool cmp(const Line &lhs, const Line &rhs) {
    return lhs.x < rhs.x;
}

int main() {
    int n;
    cin >> n;
    vector<Line> lines(n);
    for (int i = 0; i < n; ++i) {
        cin >> lines[i].x >> lines[i].y1 >> lines[i].y2;
    }
    sort(lines.begin(), lines.end(), cmp);
    for (int i = 0; i < n; i++) {
        double ansMaxSlope = 1e9;   // 枚举i线段时的最大斜率
        double ansMinSlope = -1e9;  // 枚举i线段时的最小斜率
        bool isMinSlope = false;    // 当前解是不是小斜率?
        Line ansLine{};
        int j = 0;
        for (; j < n; j++) {
            if (i == j) continue;
            double minSlope, maxSlope;
            // 这里控制红蓝线条的上下关系
            if (i < j) {
                maxSlope = (lines[i].y2 - lines[j].y1) / (lines[i].x - lines[j].x);
                minSlope = (lines[i].y2 - lines[j].y2) / (lines[i].x - lines[j].x);
            } else {
                maxSlope = (lines[i].y2 - lines[j].y2) / (lines[i].x - lines[j].x);
                minSlope = (lines[i].y2 - lines[j].y1) / (lines[i].x - lines[j].x);
            }
            // 如果不满足条件(也就是所展示的交集关系),则停止枚举
            if (maxSlope < ansMinSlope || minSlope > ansMaxSlope) break;
            // 更新最大最小斜率
            if (maxSlope < ansMaxSlope) {
                ansMaxSlope = maxSlope;
                ansLine = lines[j];
                isMinSlope = false;
            }
            if (minSlope > ansMinSlope) {
                ansMinSlope = minSlope;
                ansLine = lines[j];
                isMinSlope = true;
            }
        }
        // 如果直到最后一条线段都能满足,就说明所有线段都能满足
        if (j == n) {
            printf("%.0f %.0f %.0f %.0f", lines[i].x, lines[i].y2, ansLine.x, (isMinSlope ? ansLine.y1 : ansLine.y2));
            return 0;
        }
    }
    return 0;
}
  • 18
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「江太白」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值