P1653 猴子

猴子!好可爱!不过好难!


问题

给你许多只猴子,每一个时间段他们放手,问猴子掉落的时间。

分析

注意:这里的猴子抓猴子,不一定要手拉手,拽别人尾巴照样不会掉下去!

所以其实是一个无向图的连通问题。

每一个联通分支,答案一定是相同的。

所以可以考虑并查集来维护。而这里是一个带权的并查集。

放手是摧毁连通,想想都是几乎不可能做到的事情。

所以正难则反,我们考虑离线回答问题,从\(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;
}

转载于:https://www.cnblogs.com/Garen-Wang/p/9245551.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值