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

本文介绍了一个墓室逃脱问题的解决方案,利用深度优先遍历(DFS)确定激光线段构成的连通分量,并据此判断是否能成功通过墓室。

题目描述: 一个摸金校尉要通过一个矩形墓室,入口的位置为(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或考虑不全的地方,欢迎各位大佬给出建议。

使用单调队列可以优化动态规划中的状态转移过程,将原本需要遍历k次的内层循环优化为O(1)时间。具体来说,我们维护一个单调队列,队列中的元素按某种顺序排列,使得每次转移时可以直接从队列头部或尾部获取最优解。 ### 方法思路 1. **问题分析**:原问题中,每次可以上浮1到k层,我们需要找到在m次上浮内,能够获得的最大价值。动态规划的状态转移方程为: \[ dp[i][j] = \max_{d=1}^{k} (dp[i-d][j-1] + a[i-1]) \] 其中,`dp[i][j]`表示到达第i层,已经上浮j次时的最大价值。 2. **单调队列优化**:我们可以将状态转移方程改写为: \[ dp[i][j] = \max_{i-k \leq l \leq i-1} (dp[l][j-1]) + a[i-1] \] 这类似于滑动窗口最大值问题,可以使用单调队列来维护窗口内的最大值,将时间复杂度从O(nmk)优化到O(nm)。 3. **具体步骤**: - 对于每一层i和上浮次数j,维护一个单调队列,队列中的元素按`dp[l][j-1]`递减排列。 - 每次转移时,从队列头部获取最大值,并更新`dp[i][j]`。 - 维护队列的有效性,即队列中的元素必须在窗口[i-k, i-1]范围内。 ### 解决代码 ```cpp #include <iostream> #include <vector> #include <deque> #include <algorithm> using namespace std; int main() { int n, m, k; cin >> n >> m >> k; vector<int> a(n); for (int i = 0; i < n; ++i) { cin >> a[i]; } vector<vector<int>> dp(n + 2, vector<int>(m + 1, -1)); dp[0][0] = 0; for (int j = 1; j <= m; ++j) { deque<int> q; for (int i = 0; i <= n + 1; ++i) { // Remove elements outside the window [i - k, i - 1] while (!q.empty() && q.front() < i - k) { q.pop_front(); } // Compute dp[i][j] if possible if (!q.empty() && dp[q.front()][j - 1] != -1) { dp[i][j] = dp[q.front()][j - 1]; if (i >= 1 && i <= n) { dp[i][j] += a[i - 1]; } } // Maintain the monotonic queue if (i <= n && dp[i][j - 1] != -1) { while (!q.empty() && dp[q.back()][j - 1] <= dp[i][j - 1]) { q.pop_back(); } q.push_back(i); } } } cout << dp[n + 1][m] << endl; return 0; } ``` ### 代码解释 1. **输入处理**:读取输入的主墓室层数n,上浮次数m,每次最多上浮层数k,以及各层主墓室的价值数组a。 2. **动态规划初始化**:初始化`dp`数组,`dp[0][0]`设为0,表示从第0层开始,上浮0次时的价值为0。 3. **单调队列优化**: - 对于每一上浮次数j,维护一个单调队列q,队列中的元素是层数,按`dp[l][j-1]`递减排列。 - 遍历每一层i,维护队列的有效性,确保队列中的元素在窗口[i-k, i-1]范围内。 - 计算`dp[i][j]`时,从队列头部获取最大值,并加上当前层的价值(如果i在主墓室范围内)。 - 更新队列,保持其单调递减性质。 4. **结果输出**:输出`dp[n + 1][m]`,即到达海面时的最大价值。 通过单调队列优化,我们将时间复杂度从O(nmk)降低到O(nm),显著提高了算法的效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值