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;
}