P9150 邮箱题
P9150 邮箱题
好久没见到这么小清新的图论题了。上次见到还是在 上次。
首先,因为进入一个点时必然有该点的钥匙,而钥匙的数量不会超过进入的点的数量
考虑当前点
-
- 考虑路径上最靠近
j 的k_j 的入边,即最大的p 使得a_p\to k_j ,a_p 存在且j 可以回到a_p 。
如何判定条件二呢?因为
用并查集维护强连通分量的合并过程,我们得到了
因为
首先断环成链,将环复制两份得到序列
若从
设
用两个并查集分别维护可达链和强连通分量,预处理每个点在链上最靠近它且在它前方的入点的位置即可。
问题来了,合并两条可达链的时候,强连通分量该怎么合并?虽然产生贡献的返祖边数量总数只有
至此已经得到
我们注意到,如果两条链可以合并,那么第一条链的开头,即当前的
此外,有了该性质,在判定是否可以合并两条链时,也可以维护每个点
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
bool Mbe;
constexpr int N = 3e6 + 5;
void cmax(int &x, int y) {x = x > y ? x : y;}
int n, m, k[N], a1[N], a2[N];
bool vis[N]; // vis 表示是否被访问过
int c, cyc[N], in[N], val[N], pre[N]; // in 表示结点在环上的编号,val 表示结构编号最大的有返祖边的分量,pre 表示结点的最近前驱入边位置
vector<int> e[N];
struct dsu {
int fa[N];
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
void merge(int a, int b) {fa[find(b)] = find(a);}
} cy, ch; // 维护环(强连通分量)和链的并查集
void solve() {
cin >> n >> m;
for(int i = 1; i <= n; i++) e[i].clear();
memset(vis, 0, n + 2);
for(int i = 1; i <= n; i++) cin >> k[i];
for(int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
e[v].push_back(u);
}
for(int i = 1; i <= n; i++) {
if(vis[i]) continue;
int p = i;
while(!in[p]) cyc[++c] = p, in[p] = c, p = k[p];
for(int j = c * 2; j; j--) {
cy.fa[j] = ch.fa[j] = j, val[j] = pre[j] = 0;
int id = cyc[j > c ? j - c : j];
for(int it : e[id]) {
if(!in[it]) continue;
it = in[it];
if(it + c < j) it += c;
if(it > j) it -= c;
pre[j] = max(pre[j], it);
if(it < j) it += c;
if(it <= c * 2) cmax(val[ch.find(it)], cy.find(it));
}
while(1) {
while(1) {
int cyid = cy.find(j), chid = ch.find(j);
if(cyid < val[chid]) cy.merge(cyid + 1, cyid);
else break;
}
int cyid = cy.find(j), chid = ch.find(j);
val[chid] = 0;
if(chid == c * 2 || cyid != chid || pre[chid + 1] < j) break;
ch.merge(chid + 1, chid);
}
a1[id] = min(c, ch.find(j) - j + 1);
a2[id] = min(c, cy.find(j) - j + 1);
}
for(int i = 1; i <= c; i++) in[cyc[i]] = 0, vis[cyc[i]] = 1;
c = 0;
}
for(int i = 1; i <= n; i++) cout << a1[i] << " " << a2[i] << "\n";
}
bool Med;
int main() {
fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
#ifdef ALEX_WEI
FILE* IN = freopen("1.in", "r", stdin);
FILE* OUT = freopen("1.out", "w", stdout);
#endif
int T;
cin >> T;
while(T--) solve();
cerr << 1e3 * clock() / CLOCKS_PER_SEC << " ms\n";
return 0;
}