会松手的猴子

题目链接

https://www.luogu.org/problemnew/show/P1653
http://172.20.6.3/Problem_Show.asp?id=1694


题目描述

题目描述
有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


思路

首先看到这道题的时候想到的是暴力,或者是模拟,但是我不会
之后开始我们的正文,这是我近来学习并查集的最后一个内容,倒序并查集
因为没有思路,所以我看了题解+问了石神才把这道题搞懂了,但是这道题并不是说不再处理了,而是之后要强加联系。

解题报告:

  1. 简化题面:对于这道题而言可以看成是断开以后的若干个连通块的问题,若是当前的连通块中有1号节点,那么这个连通块就始终不会掉落,反之,其他的连通块是一定会掉落的。
  2. 理清算法:对于若干个连通块的问题的合并,查询,断开,我们首先考虑的当然是并查集,但是,这样的话,就又有了一个麻烦事儿,并查集怎么断开??
    既然不会,就换个思路,我们倒着做并查集,我们可以倒着加边。原来的正着删边的结束条件是什么时候与1号节点断开连接,那么倒着加边的算法的结束条件就是什么时候与1号节点建上联系。
  3. 思路总结:我们首先要进行n个前提的记录,之后是要有m个断开条件的标记,最后是并查集的处理,倒序加边。

写这道题的时候存在的一些问题:

Q:为什么最后要倒序处理?
A:因为正序的搞的并查集的断开,很难,所谓我们此时应该换一个思路。我们倒序,变成是并查集的加边,把断开的时间转化成是连上第一个节点的时间

Q:为什么这道题可以用并查集做?
A:

  1. 因为这道题可以看成是若干个连通块的问题,如果有点始终存在于1号的连通块内,那么就始终不会掉落,那么其他的会掉落的连通块就是看根节点的最早的掉落时间即是整个连通块的掉落时间。若干个连通块的问题的连接和合并(或者是断开)就可以用并查集问题。
  2. 但是此时并查集的断开需要更加高深的知识(我不会),所以我们就可以倒序处理这m条关系,换成是加边。
    末状态倒推初状态,判断最后连接到第一只猴子的时间就是这一坨猴子掉下的时间。
    对于各个点都用并查集维护,它掉落的时间由他的所有的祖先中松手最早的那一只猴子决定。

code实现

#include<bits/stdc++.h>
using namespace std;

const int nn=200010;
const int mm=400010;

int n,m;
int fa[nn],ans[nn];
int son[nn][3],bj[3][nn];

struct monkey
{
	int id,l;
}k[mm];

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

int gf(int x)
{
	if(x==fa[x])	return x;
	int root=gf(fa[x]);
	ans[x]=min(ans[x],ans[fa[x]]);
	return fa[x]=root;
}

void Union(int x,int y,int z)
{
	int xx=gf(x), yy=gf(y);
	if(xx!=yy)/*错因1:赋值的大前提是xx!=yy*/
	{
		if(xx==1)	{fa[yy]=xx; ans[yy]=z;}
		else	{fa[xx]=yy; ans[xx]=z;}
	}
}

int main()
{
	n=read(), m=read();
	for(int i=1;i<=n;++i)
	{
		ans[i]=1e9, fa[i]=i;
		son[i][1]=read(), son[i][2]=read();/*记录i节点的左手或者是右手连接的点*/
	}
	for(int i=1;i<=m;++i)
	{
		k[i].id=read(); k[i].l=read();/*保存放手信息,以便于后续的操作*/
		bj[k[i].l][k[i].id]=true;/*标记信息,证明这一条信息已经被选择,是以后要删除或者是移除的信息*/
	}
	
	for(int i=1;i<=n;++i)
	{
		if(!bj[1][i]&&son[i][1]!=-1)/*这些是不在m条信息中的点i,son[i][1]是不会被移除的,那么此时就要找到他们的部队,若根节点是1,就说明不会被移除;否则,是一定会被移除的。*/
			Union(i,son[i][1],1e9);
		if(!bj[2][i]&&son[i][2]!=-1)/*错因3:在这里多加了一个else*/
			Union(i,son[i][2],1e9);/*注意,这里的z=1e9,对答案并没有影响,因为答案一开始赋值的就是1e9,而且在此之前没有任何修改操作*/
	}

	for(int i=m;i>=1;--i)/*记得倒序操作,转化成是加边处理*/
		if(son[k[i].id][k[i].l]!=-1)/*m条信息,这样的话就要为m条信息编上时间,是自己程序性死亡的初始时间*/
			Union(k[i].id,son[k[i].id][k[i].l],i-1);/*判断这两个点什么时候连上边(相互脱离)*/
	printf("%d\n",-1);/*1号节点是一定不会被移除的节点,可以直接输出-1*/
	for(int i=2;i<=n;++i)
	{
		gf(i);
		if(ans[i]==1e9)	printf("-1\n");/*如果是与1号节点直接相连的节点的话,那么就一定不会掉落*/
		else			printf("%d\n",ans[i]);
	}
	return 0;
} 

代码之中是有一些细节的。

问题:在原代码中,有一个操作是把所有的与1联通的点的答案全部赋值为1e9,但是我发现这个操作可以完全不必要,因为在这个操作之前,完全没有修改ans的操作。

注意注意,在gf函数中应该是有两个return的,不要忘打了一个,这样的话就会一直跑下去,然后,你的程序就挂掉了!!

附上自己看的一篇题解的链接 https://www.luogu.org/problemnew/solution/P1653


一直努力着,适应着这个世界的温度,不管是季节还是人心。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值