猴子!好可爱!不过好难!
问题
给你许多只猴子,每一个时间段他们放手,问猴子掉落的时间。
分析
注意:这里的猴子抓猴子,不一定要手拉手,拽别人尾巴照样不会掉下去!
所以其实是一个无向图的连通问题。
每一个联通分支,答案一定是相同的。
所以可以考虑并查集来维护。而这里是一个带权的并查集。
放手是摧毁连通,想想都是几乎不可能做到的事情。
所以正难则反,我们考虑离线回答问题,从\(m-1\)到\(0\)时间段倒着算答案。
那么就是一个添加连通的问题了,直接并查集merge即可。
我们维护ans数组,表示猴子掉下去的时间。
1号猴子吊在树上,怎么都不会掉,所以\(ans[1] = INF\)
首先当然要连上\(m\)时间段中,还剩下的边。
然后就可以直接逆序合并集合,回答问题了。
如何维护ans?
这也许是最难的地方了。如果没有学过带权并查集的话,绝对想不出来。我就想不出来
给出代码:
int find(int x)
{
if(fa[x] == x) return x;
int temp = find(fa[x]);
ans[x] = std::min(ans[x], ans[fa[x]]);
return fa[x] = temp;
}
这个东西什么意思?
一个点的ans,是这个联通分支中最小的ans。
注意一定要先递归,不然从下面弄上去是错的。
先递归的话,先执行的是第二层、第三层,以此类推到叶子。
如果不懂的话就死记吧。。。
其他的东西都是细节,说不了。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
const int maxn = 200005, maxm = 400005, INF = 0x3f3f3f3f;
int n, m;
struct Node//the situation of loosing hand
{
int u, hand;
} s[maxm];
int G[maxn][2];
bool b[maxn][2];
int ans[maxn];
int fa[maxn];
int read()
{
int ans = 0, s = 1;
char ch = getchar();
while(ch > '9' || ch < '0')
{
if(ch == '-') s = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
ans = ans * 10 + ch - '0';
ch = getchar();
}
return s * ans;
}
int find(int x)
{
if(fa[x] == x) return x;
int temp = find(fa[x]);
ans[x] = std::min(ans[x], ans[fa[x]]);
return fa[x] = temp;
}
void merge(int x, int y, int k)
{
x = find(x), y = find(y);
if(x != y)
{
if(x == 1) fa[y] = x, ans[y] = k;
else fa[x] = y, ans[x] = k;
}
}
int main()
{
n = read(), m = read();
for(int i = 1; i <= n; i++)
{
fa[i] = i;
G[i][0] = read(), G[i][1] = read();
}
for(int i = 0; i < m; i++)
{
s[i].u = read(), s[i].hand = read() - 1;
b[s[i].u][s[i].hand] = true;
}
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= 1; j++)
{
if(!b[i][j] && G[i][j] != -1) merge(i, G[i][j], m);
}
}
memset(ans, 0x3f, sizeof(ans));
for(int i = m - 1; i >= 0; i--)
{
if(G[s[i].u][s[i].hand] != -1) merge(s[i].u, G[s[i].u][s[i].hand], i);
}
for(int i = 1; i <= n; i++)
{
find(i);
if(ans[i] == INF) printf("-1\n");
else printf("%d\n", ans[i]);
}
return 0;
}