19 S2模拟赛T1 博物馆验收 题解

博物馆验收

题面

给定 \(n\) 个房间,第 \(i\) 个房间有 \(d_i\) 个门,按照给出的顺序编号为 \(1 \sim d_i\)

如果当前房间为起点,从 \(1\) 号门走到下一个房间。

否则,设当前在 \(x\) 房间,将进来时通过的门上对应的编号记为 \(id\) ,如果 \(id = d_x\) ,那么走 \(1\) 号门,否则走 \(id + 1\) 号门,不断重复上述过程。

对于每个房间,求出以其为起点经过不同的边的数量。

\(1 \le n \le 2 \times 10^5, 1 \le d_i \le 3\)

题解

这道题场上是真没想到怎么做,后来和 xpigeon 讨论了一下才发现了这题的性质,其实是诈骗题

我们考虑对于每扇门,其下一扇门是一定的,其上一扇门也是一定的,这是由移动规则保证的。

不难发现,按照上述移动规则,永远也不会停下来,所以一定是在这些门之间不断循环。

\(f(x,i)\) 表示以 \(x\) 房间的第 \(i\) 扇门为起点经过的不同边的数量。

对于某个门,如果我们没有遍历过,那就将其所在的环遍历一遍,环上每个点的答案都是环的大小。

这样的时间复杂度为 \(O(n)\) ,因为每个门至多遍历一次。

至于判重边,可以用 map。

code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <map>

using namespace std;

namespace michaele {

    const int N = 2e5 + 10;

    int n;
    int d[N], ver[N][4];
    int f[N][4];
    map <pair <int, int>, bool> mp;
    bool vis[N][4];

    int dfs (int x, int id, int cnt) {
        auto &now = f[x][id];
        if (~now) return now;
        if (vis[x][id]) return now = cnt;
        vis[x][id] = 1;
        int y = ver[x][id];
        // 判断 (x, y) 这条边是否计算过
        if (!mp.count ({x, y}) && !mp.count({y, x})) {
            cnt ++;
            mp[{x, y}] = 1;
        }
        for (int i = 1; i <= d[y]; i ++) {
            if (ver[y][i] == x) id = i;
        }
        return now = dfs (y, id == d[y] ? 1 : id + 1, cnt);
    }

    void solve () {
        ios :: sync_with_stdio (0);
        cin.tie (0);
        cout.tie (0);

        memset (f, -1, sizeof f);
        cin >> n;
        for (int i = 1; i <= n; i ++) {
            cin >> d[i];
            for (int j = 1; j <= d[i]; j ++) {
                cin >> ver[i][j];
            }
        }

        // cout << dfs (2, 1, 0) << endl;
        for (int i = 1; i <= n; i ++) {
            map <pair <int, int>, bool> emp;
            mp.swap (emp);
            cout << dfs (i, 1, 0) << endl;
        }
    }
}


int main () {

    michaele :: solve ();

    return 0;
}
posted @ 2025-10-05 17:56  michaele  阅读(7)  评论(0)    收藏  举报