2017阿里实习生招聘编程题之墓室

题目描述: 一个摸金校尉要通过一个矩形墓室,入口的位置为(0,0),出口位置为(m,n)。墓室中散步着一些散光发射器,某些激光发射器之间有激光。m,n和激光的起始和终止位置(x1,y1,x2,y2)均为整数。请问他能否不碰到激光,成功通过墓室。

这里写图片描述

题目分析: 首先通过深度优先遍历(DFS)根据直线是否相交确定所有的激光线段所构成的连通分量,例如,右图有3个连通分量,分别记录每个连通分量的横坐标的最小值和最大值,纵坐标的最小值和最大值。 那么当有连通分量横坐标最小最大值分别为0和m, 或纵坐标最小最大值为0和n, 或横、纵坐标最小值都为m ,或横、纵坐标的最大值分别为m和n时,不能通过墓室,这样求很容易写出程序了。

#include<iostream>
#include<algorithm>
#include<vector>
int cnt = 1;   //cnt定义图的连通分量的个数, 第一个连通分量(cnt=1),第2个(cnt=2),依次类推
struct Point   //point为定义的坐标(x,y)
{
    int x;
    int y;
};
struct lt   //存放每个连通分量的 横,纵坐标的最小最大值
{
    int x_min;    //横坐标最小值
    int x_max;
    int y_min;
    int y_max;
};
struct lt liantong[100];   //存放每个连通分量的4个最大最小坐标值
int visited[100] = { 0 };  //标记该条线是否被访问,  线之间是否相连的连通分量的个数
using namespace std;

double mult(Point a, Point b, Point c)  //相当于求斜率   K(bc)-K(ba)
{
    return (a.x - c.x)*(b.y - c.y) - (b.x - c.x)*(a.y - c.y);
}
//aa, bb为一条线段两端点 cc, dd为另一条线段的两端点 相交返回true, 不相交返回false  
bool intersect(Point aa, Point bb, Point cc, Point dd)   //判断2条直线是否相交
{
    if (max(aa.x, bb.x)<min(cc.x, dd.x))
    {
        return false;
    }
    if (max(aa.y, bb.y)<min(cc.y, dd.y))
    {
        return false;
    }
    if (max(cc.x, dd.x)<min(aa.x, bb.x))
    {
        return false;
    }
    if (max(cc.y, dd.y)<min(aa.y, bb.y))
    {
        return false;
    }
    if (mult(cc, bb, aa)*mult(bb, dd, aa)<0)
    {
        return false;
    }
    if (mult(aa, dd, cc)*mult(dd, bb, cc)<0)
    {
        return false;
    }
    return true;
}

void DFS(vector<Point>& vec1, vector<Point>& vec2, int k, int visited[100])    //深度优先确定连通分量的个数
{
    visited[k] = cnt;
    for (int i = 0; i<vec1.size(); i++)
    {
        if (visited[i] == 0)  //如果还没有被访问过
        {
            if (intersect(vec1[k], vec2[k], vec1[i], vec2[i]))  //如果k和i 这2条直线连通
                DFS(vec1, vec2, i,visited);
        }
    }
}
int main()
{
    int i, j, m, n, k;
    cin >> m >> n;
    cout << "请输入激光的条数:" << endl;
    cin >> k;
    cout << "请输入k个激光的坐标,每条线有4个点,分别为坐标(x1,y1),(x2,y2):" << endl;
    vector<Point> vec1, vec2;
    for (i = 0; i<k; i++)
    {
        Point point1, point2;
        cin >> point1.x >> point1.y >> point2.x >> point2.y;
        vec1.push_back(point1);
        vec2.push_back(point2);
    }
//  int *visited = new int[k];  //标记该条线是否被访问,  线之间是否相连的连通分量的个数 
//  memset(visited, 0, sizeof(visited));
    for (i = 0; i<k; i++)
    {
        if (visited[i] == 0)
        {
            DFS(vec1, vec2, i, visited);
            liantong[cnt].x_min = min(vec1[i].x, vec2[i].x);
            liantong[cnt].x_max = max(vec1[i].x, vec2[i].x);
            liantong[cnt].y_min = min(vec1[i].y, vec2[i].y);
            liantong[cnt].y_min = max(vec1[i].y, vec2[i].y);
            for (j = 0; j<k; j++)
            {
                if (visited[j] == cnt) //如果该线段属于第cnt个连通分量
                {
                    if (min(vec1[j].x, vec2[j].x)<liantong[cnt].x_min)
                        liantong[cnt].x_min = min(vec1[j].x, vec2[j].x);

                    if (max(vec1[j].x, vec2[j].x)>liantong[cnt].x_max)
                        liantong[cnt].x_max = max(vec1[j].x, vec2[j].x);

                    if (min(vec1[j].y, vec2[j].y)<liantong[cnt].y_min)
                        liantong[cnt].y_min = min(vec1[j].y, vec2[j].y);

                    if (max(vec1[j].y, vec2[j].y)>liantong[cnt].y_max)
                        liantong[cnt].y_max = max(vec1[j].y, vec2[j].y);
                }
            }
            cnt++;
        }
    }
    bool flag = false;
    for (i = 1; i <= cnt; i++)  //一共cnt个连通分量
    {              //直线连通分量把密室完全挡住了
        if ((liantong[i].x_min == 0 && liantong[i].x_max == m) || (liantong[i].y_min == 0 && liantong[i].y_max == n)|| (liantong[i].x_min == 0 && liantong[i].y_min == 0) || (liantong[i].x_max == m && liantong[i].y_max == n))
        {
            cout << "不能成功通过密室" << endl;
            flag = true;
            break;
        }
    }
    if (!flag)
        cout << "可以通过密室" << endl;
    system("pause");
    return 0;
}

输入:
这里写图片描述

由于时间仓促,代码可能有些bug或考虑不全的地方,欢迎各位大佬给出建议。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值