【并查集】猴子

题目描述

有N只猴子,第一只尾巴挂在树上,剩下的N-1只,要么被其他的猴子抓住,要么抓住了其他的猴子,要么两者均有。当然一只猴子最多抓两只另外的猴子。现在给出这N只猴子抓与被抓的信息,并且在某个时刻可能某只猴子会放掉它其中一只手的猴子,导致某些猴子落地。求每只猴子落地的时间。

输入输出格式

输入格式:

第一行两个数N、M,表示有N只猴子,并且总时间为M-1。接下来N行,描述了每只猴子的信息,每行两个数,分别表示这只猴子左手和右手抓的猴子的编号,如果是-1,表示该猴子那只手没抓其他猴子。再接下来M行,按时间顺序给出了一些猴子放手的信息,第1+N+i行表示第i-1时刻某只猴子的放手信息,信息以两个数给出,前者表示放手的猴子编号,后者表示其放的是哪只手,1左2右。

【数据规模】

30%的数据,N≤1000,M≤1000;

100%的数据,1≤N≤200000,1≤M≤400000。

输出格式:

共输出N行,第i行表示第i只猴子掉落的时刻,若第i只猴子岛M-1时刻以后还没掉落,就输出-1。

输入输出样例

输入样例#1:
3 2
-1 3
3 -1
1 2
1 2
3 1
输出样例#1:
-1
1
1





















分析:这题其实就是个无向的连通图,求删除一条边后图的连通性。
正向去删边判断确实有点麻烦,我们不妨把操作存起来,从最后一个操作开始逆向加边,

当两个点第一次连通时,就是那两只猴子掉下来的时候。
这里用到带权并查集,也是把每个点的权值都赋为他祖宗的权值,而权值就是掉下来的时间。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <set>
#include <map>
#include <vector>
#include <list>
#define Open(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout);
#define Close fclose(stdin);fclose(stdout);
#define rep(i,m,n) for(int i=m;i<=n;i++)
#define dop(i,m,n) for(int i=m;i>=n;i--)
#define lowbit(x) (x&(-x))
#define ll long long
#define INF 2147483647
using namespace std;
inline int read(){   //快读 
    int s=0,w=1;
    char ch=getchar();
    while(ch<='0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
    return s*w;
}
const int maxn=200010;
const int maxm=400010;
int n,m;
int s[2][maxn],f[maxn],ans[maxn]; //s存左右儿子,s[0]是左,s[1]是右,f:并查集,ans:答案 
bool v[2][maxn];    //v存要断的边,和邻接矩阵原理一样
struct fame{int f,s;}e[maxm];
int find(int x){    //带权并查集 
    if(f[x]==x) return x;
    int temp=find(f[x]);
    ans[x]=min(ans[x],ans[f[x]]);
    return f[x]=temp;
}
void get_fa(int x,int y,int z){     //x,y是要连的两只猴子,z是时间 
    x=find(x),y=find(y);
    if(x!=y){
      if(x==1) f[y]=x,ans[y]=z;
      else f[x]=y,ans[x]=z;
    }
}
int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++){
       s[0][i]=read();s[1][i]=read();
       f[i]=i;ans[i]=maxm;
    }
    for(int i=1;i<=m;i++){
       scanf("%d%d",&e[i].f,&e[i].s);
       v[e[i].s-1][e[i].f]=1;
    }
    for(int i=1;i<=n;i++){
       if(!v[0][i]&&s[0][i]!=-1) get_fa(i,s[0][i],m);  //把有的边连上 
       if(!v[1][i]&&s[1][i]!=-1) get_fa(i,s[1][i],m);
    }
    for(int i=1;i<=n;i++) ans[i]=maxm;   //初始值赋为一个极大数 
    for(int i=m;i>=1;i--) if(s[e[i].s-1][e[i].f]!=-1) get_fa(e[i].f,s[e[i].s-1][e[i].f],i-1); //逆向连边,更新ans 
    for(int i=1;i<=n;i++) find(i),printf("%d\n",ans[i]==maxm?-1:ans[i]);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值