P9150 邮箱题 题解

P9150 邮箱题 题解

注意到 \(n^2\) 的做法给了足足 70pts,那让我们先来考虑如何 \(n^2\) 解决这个问题。

这样一来我们可以暴力枚举每个点走动的过程。由于 \(k\) 是一个排列,换句话说当你踏入一个点时你下一个要去的点已经确定了。现在要研究的是如何判定能否到达那个点。设当前的点为 \(x\),你要去的点为 \(k_x\),那么要走到 \(k_x\) 意味着你首先只能走已走过的点,换句话说你要维护一个路径上经过的点组成的一个个强连通分量,检查 \(x\) 所在强连通分量有没有终点为 \(k_x\) 的边,有的话就合法,然后更新合并强连通分量。

对于 \(O(n)\) 的做法,一个个算显得不太现实,考虑如何均摊。注意到我们每次维护的实际上是 \(k\) 形成的排列一次次循环的过程,实际上有很多冗余,那么不妨把 \(k\) 排列中一个个轮换环提出来,对每一个环分别维护。

那么对每个环断环成链复制一下来维护,从后往前倒序遍历每个点来维护其之后通达性情况。加入一个点后,我们需要先维护这个点能否与上一个点所在的强连通分量有连边,以此检查能否通达。为了回答第一问,我们还需要维护每个点最右能走到的位置,我们称之为区间,维护区间最右端即可。之后我们需要用一些指针维护边来合并相邻的连通块与区间,这样就做完了。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 3e6 + 5;
int n, m;
int k[N];
vector<int>v[N];
struct BCJ {
	int fa[N];
	int fnd(int x) {
		return x == fa[x] ? x : fa[x] = fnd(fa[x]);
	}
	void mge(int x, int y) {
		x = fnd(x), y = fnd(y);
		fa[x] = y;
	}
} P, Q;
int p[N], q[N];
bool vis[N];
int stk[N], top;
int num[N];
int ans1[N], ans2[N];
void sve(int w) {
	while (!vis[w]) {
		vis[w] = 1;
		stk[++top] = w;
		num[w] = top;
		w = k[w];
	}
	for (int i = top * 2; i; --i) {
		P.fa[i] = Q.fa[i] = i;
		p[i] = q[i] = 0;
		int x = stk[(i - 1) % top + 1];
		for (int y : v[x]) {
			if (!num[y]) continue;
			y = num[y];
			if (y + top < i) y += top;
			if (y > i) y -= top;
			q[i] = max(q[i], y);
			if (y < i) y += top;
			if (y <= top * 2) p[Q.fnd(y)] = max(p[Q.fnd(y)], P.fnd(y));
		}
		while (1) {
			while (1) {
				int x = P.fnd(i), y = Q.fnd(i);
				if (x < p[y]) P.mge(x, x + 1);
				else break;
			}
			int x = P.fnd(i), y = Q.fnd(i);
			p[y] = 0;
			if (x != y || q[y + 1] < i || y == 2 * top) break;
			Q.mge(y, y + 1);
		}
		ans1[x] = min(Q.fnd(i) - i + 1, top), ans2[x] = min(P.fnd(i) - i + 1, top);
	}
	for (int i = 1; i <= top; i++) num[stk[i]] = 0, stk[i] = 0;
	top = 0;
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	int T;
	cin >> T;
	while (T--) {
		cin >> n >> m;
		for (int i = 1; i <= n; i++) cin >> k[i];
		while (m--) {
			int x, y;
			cin >> x >> y;
			v[y].push_back(x);
		}
		for (int i = 1; i <= n; i++)
			if (!vis[i]) sve(i);
		for (int i = 1; i <= n; i++) cout << ans1[i] << ' ' << ans2[i] << '\n', v[i].clear(), vis[i] = 0;
	}
	return 0;
}
posted @ 2025-07-01 22:03  长安19路  阅读(17)  评论(0)    收藏  举报