[Poi2003/BZOJ2610] Monkeys 题解
题目描述
思路
猴子 \(i\) 掉下去用科学地语言来说就是 \(i\) 与 \(1\) 号点不在一个集合中。
正向地来做,删边操作不好实现,因此每次暴力建一个图,这样时间复杂度太高了,不能接受。
可以发现每次暴力建出来的图实际上只有 “一边之差”,那么我们可不可以利用这个性质去优化时间复杂度呢?
实际上,可以考虑逆向地来做这道题,这样可以发现猴子每次松手反过来只是在逆序图上加了一个边而已,因此可以逆序离线处理每次查询,这么做时间复杂度就得到了极大的优化。
梳理一下思路
- 先处理出来所有猴子松手后的最终图;
- 逆序处理,如果此次加边连通了 \(1\) 和集合 \(A\),那么意味着集合 \(A\) 中的所有猴子在此刻掉了下去,用 \(\text{DFS}\) 把集合 \(A\) 中所有元素的答案更新一遍;
- 输出答案。
总时间复杂度:\(O(m\alpha (n) + n)\) 或 \(O(m\log n + n)\)。
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
const int N = 2e5 + 10, M = 4e5 + 10;
int hand[N][3], off[M][3];
bool is_off[N][3];
int fa[N], ans[N], rk[N];
int find(int x)
{
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
vector<int> g[N];
void merge(int a, int b)
{
int x = find(a), y = find(b);
g[a].push_back(b);
g[b].push_back(a);
if (x != y)
{
if(rk[x] < rk[y]) fa[x] = y;
else if(rk[x] > rk[y]) fa[y] = x;
else fa[x] = y, rk[y] ++;
}
}
bool st[N];
void dfs(int p, int tm)
{
ans[p] = tm, st[p] = true;
for (auto i : g[p])
{
if (!st[i])
dfs(i, tm);
}
st[p] = false;
}
int main()
{
int n, m;
cin >> n >> m;
memset(ans, -1, sizeof ans);
for (int i = 1; i <= n; i++)
cin >> hand[i][1] >> hand[i][2], fa[i] = i, rk[i] = 1;
for (int i = 1; i <= m; i++)
{
cin >> off[i][1] >> off[i][2];
is_off[off[i][1]][off[i][2]] = true;
}
for (int i = 1; i <= n; i++)
{
if (!is_off[i][1] && ~hand[i][1])
merge(i, hand[i][1]);
if (!is_off[i][2] && ~hand[i][2])
merge(i, hand[i][2]);
}
for (int i = m; i >= 1; i--)
{
int a = hand[off[i][1]][off[i][2]], b = off[i][1];
swap(a, b);
int x = find(a), y = find(b);
if (x == y)
continue;
if (x == find(1))
{
dfs(y, i - 1);
merge(x, y);
}
else if (y == find(1))
{
dfs(x, i - 1);
merge(x, y);
}
else
merge(x, y);
}
for (int i = 1; i <= n; i++)
cout << ans[i] << '\n';
return 0;
}