2024牛客暑期多校训练营1 I题

题目说明:

题目给定n行m列的镜子,字符表示为

    ‘\' ,'/' :表示斜着的镜子,可以反射四个方向

    '-' , '|',表示横着,竖着的镜子,可以反射两个方向

注:  (碰到边界则光线将消失)

将给定个q,表示q次操作,每次操作如下:

   每次操作将一束光线从坐标(x,y)处发射

   方向为{above,below,left,right}的其中一种

   例如{1,2,below}表示从坐标为(1,2)处发射一个方向向下的光线

求:最终光线能从多少个不同的镜子反射

说明:思路实际上并不难,因为光线最终只有两种可能,一种是链状,一种是环状,困难的地方在于实现细节,因此本篇重点从实现细节入手。

不同点:该题目比较特殊的地方是每个坐标都有四个方向,因此相对于普通的搜索,我们还需要考虑方向

一.使用的变量记录

将代码中用到的变量记录下来,防止有人不知道变量的含义

int c[1005][1005];//记录图,'\','/','-','|'分别表示0,1,2,3
int ans[1005][1005][4];//表示为坐标为x,y时,接收方向为k的光线的答案
int target[4][4] = { 2,3,0,1,3,2,1,0,1,0,2,3,0,1,3,2 };//用于记录反射后的光线
int mx[4] = {-1,1,0,0};//改变的x坐标
int my[4] = { 0,0,-1,1 };//改变的y坐标
bool st[1005][1005];//坐标(x,y)是否加入过答案中
bool vis[1005][1005][4];//记录坐标(x,y),接收方向为k的光线是否跑过dfs
int in[1005][1005][4];//表示坐标为(x,y),接收方向为k的光线的入度
set<pair<int, int>>s;//记录走过的反射点坐标,利用set可去重

二.区分出链状与环状

   1.可以参考拓补排序,让每个坐标被射入4个方向的射线,看看能够反弹到哪里,反弹到的位置入度+1

    //0,1,2,3分别表示上下左右
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = 0; k < 4; k++) {
                int tk = target[c[i][j]][k];//tk表示光线经过该镜子后的方向
                int tx = i + mx[tk];//下一步的x坐标
                int ty = j + my[tk];//下一步的y坐标
                in[tx][ty][tk]++;
            }
        }
    }

     2.此后对入度为0的点,跑dfs,因为环状光线中,不会存在入度为0的点,实际上该步骤是在跑链状光线

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = 0; k < 4; k++) {
                if (in[i][j][k] != 0)continue;
                dfs(i, j, k);
                //跑完dfs需要清空记录的反射点
                for (auto x : s) {
                    st[x.first][x.second] = 0;
                }
                s.clear();
            }
        }
    }

   3. 跑完链状光线后,剩下未跑的点都是环状光线的。因为跑dfs时,用vis记录过跑过的点,因此仅需跑vis值为0的点,也就是未跑的点

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            for (int k = 0; k < 4; k++) {
                if (!vis[i][j][k]) {
                    s.clear();//s记录每次dfs过程中的反射点,要初始化
                    dfs(i, j, k);
                }
            }
        }
    }

三.dfs过程中的细节

dfs实际上可以分成链状的dfs与环状的dfs,

1.对于链状的dfs,我们需要跑到结尾时,再倒着得到答案,并且需要记录反射点,遇到相同的反射点答案是不能增加的

如下图,共发生三次反射,可以看作是终点处,倒着遇到每个(不同)反射点,该点ans[now]=ans[last]+1,最终可以得到该链上每个点的答案

void dfs1(int x, int y, int k) {
    if (x == 0 || x == n + 1 || y == 0 || y == m + 1) {
        return;
    }
    vis[x][y][k] = 1;
    int tk = target[c[x][y]][k];
    int tx = x + mx[tk];
    int ty = y + my[tk];//求出下一步的目的坐标与光线方向
    int plus = 0;
    dfs1(tx, ty, tk);    //一定要先dfs到结尾,再倒着记录ans
    //发生反射,且当前反射点未用过才+1
    if (!st[x][y] && tk != k) {
        st[x][y] = 1;
        plus++;
    }
    ans[x][y][k] = ans[tx][ty][tk] + plus;
}

2.而对于环状dfs,环中每个结点的大小均为环中反射点的数量,因此仅需用set(可以自动去重)记录每个反射点,最终令结点均等于set.size大小即可

int dfs2(int x, int y, int k) {
    if (vis[x][y][k])return s.size();
    vis[x][y][k] = 1;
    int tk = target[c[x][y]][k];
    int tx = x + mx[tk];
    int ty = y + my[tk];//求出下一步的目的坐标与光线方向
    if (tk != k) {
        s.insert({ x,y });
    }
    int temp = dfs(tx, ty, tk);
    //结点上的每个点答案均为(环中反射点数量),也就是s.size()
    return ans[x][y][k] = temp;
}

四.输出答案

由于每次操作给的是,从坐标(x,y)射出某方向的光线,而非被射入,因此需要将x,y改为下一步的坐标

for (int i = 1; i <= q; i++) {
        int x, y;
        string s;
        cin >> x >> y >> s;
        if (s == "above") {
            cout << ans[x - 1][y][0] << endl;
        }
        else if (s == "below") {
            cout << ans[x + 1][y][1] << endl;
        }
        else if (s == "left") {
            cout << ans[x][y - 1][2] << endl;
        }
        else cout << ans[x][y + 1][3] << endl;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值