1022 合纵连横【并查集 元素的删除】

合纵连横

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 3
描述

乱世天下,诸侯割据。每个诸侯王都有一片自己的领土。但是不是所有的诸侯王都是安分守己的,实力强大的诸侯国会设法吞并那些实力弱的,让自己的领土面积不断扩大。而实力弱的诸侯王为了不让自己的领土被吞并,他会联合一些其他同样弱小的诸侯国,组成联盟(联盟不止一个),来共同抵抗那些强大的诸侯国。 强大的诸侯国为了瓦解这些联盟,派出了最优秀的间谍来离间他们,使一些诸侯国退出联盟。最开始,每个诸侯国是一个联盟。

有两种操作

1、U x y 表示xy在同一个联盟。0x,y<n

2、D x   表示x退出联盟。

输入
多组测试数据
第一行两个数,n和m(1 ≤ n≤ 10^5, 1 ≤ m ≤10^5),分别表示诸侯国的个数和操作次数。
接下来有m行操作
输出
输出联盟的个数
样例输入
5 7
U 0 1
U 1 2
U 0 3
D 0
U 1 4
D 2
U 0 2
10 1
U 0 9
样例输出
Case #1: 2
Case #2: 9


刚看到这个题的时候,很明显的是并查集的知识,然后就开敲代码,然后写着写着发现不对了........

同以往的不一样的是,平常的题只是往里面加元素,判断连通或者集合的个数之类的,但是这个题,需要删去某个元素,当然,如果这个元素不是父节点,直接让这个元素直接保存为自己的编号值就行(自己是自己的父节点),但是如果自身是父节点的时候,直接这样操作就会把这个集合打乱,后边的操作就肯定不对了,然后想了想,没解决办法,毕竟数据和现实不一样,并查集里一个集合里的元素,是由一个父节点联系起来的,而不是所有的元素之间都有联系,可以随便删除,然后上网找并查集删除的相关内容,不过都不够详细,然后自己靠自己的理解说其中一种方法,方法不在多,关键在自己理解。

有个方法是用个辅助集合标记来做的,自己也不是完全理解,但是好像懂了点,用我代码里面的数据来说说自己的理解,一个per 数组,和往常一样,保存哪几个数据之间有关系,一个辅助 v 数组保存的值和它每个下标都相同,相当于现在某个元素,都有了两个,那么操作的时候,合并和查找的时候,对数组 v 的元素或者下标进行对应的并查集的操作可以实现相同的功能,然后需要执行删除操作的时候,就开始“删除”操作,这里的删除其实要做的并不是把某个元素真正从集合里面删除掉了,他和别的元素的关系还在,但是这个元素的真正位置已经移到了 per 数组的其他区域(不会与别的元素区域重合),而且呢,v 数组相应的坐标的元素里面储存着这个元素的位置,不至于最后找不到它是第几号元素,等到下一次操作如果需要拿这个元素和别的元素合并的话,就是相当于先从 v 数组里面找到这个元素现在放的位置,然后在per 数组里对这个位置的元素进行合并和查找等操作,所有的操作都这样做,也就相当于这个元素虽然在per 数组里出现了好几次,但是它真正的位置在 v 数组里面,这样的方法也就相当于间接进行 per 数组里面元素的操作了,但是在操作的时候和一般的方法一样,  某个元素在per数组里面的真正有效位置(也就是操作的下标)保存在 v 数组里它对应的下标中,一般的并查集操作则是per数组的下标就是对应元素自身,算是直接访问了当前元素,而这样用辅助数组的方法,算是间接的访问某个元素的位置。

遍历有几个集合,那么直接就访问 v 数组,每次都取出v 数组里面保存的当前这个元素在 per 数组里真正的位置,然后找到他的根节点,统计个数,并且直接标记上,只有下一次根节点不一样的时候,才更新统计数量,最后就可以得到具体有多少集合了...

ps:自己理解的不深,也在想怎么用更好的方法来统计不同的集合的数量,个人意见,不足之处还请大神指教微笑


#include<stdio.h>
#include<string.h>
int per[100005],v[100005],mark[100005],n;// 1 万个数据,来回运算,需要很大的空间,而且这个方法的确很浪费空间...
void init()//初始化
{
	for(int i=0;i<n;++i)
	{
		per[i]=i;
		v[i]=i;
	}
}
int find(int x)//查找
{
	int r=x;
	while(r!=per[r])
	{
		r=per[r];
	}
	int i=x,j;
	while(i!=r)//经典的路径压缩
	{
		j=per[i];per[i]=r;i=j;
	}
	return r;
}
void join(int x,int y)//合并
{
	int fx=find(x),fy=find(y);
	if(fx!=fy)
	{
		per[fy]=fx;
	}
}
int main()
{
	int t=0,i,m,a,b,k;char s;
	while(~scanf("%d%d",&n,&m))
	{
		init();k=n;//现在k指的位置处空白
		memset(mark,0,sizeof(mark));
		for(i=0;i<m;++i)
		{
			scanf(" %c",&s);
			if(s=='U')
			{
				scanf("%d%d",&a,&b);
				join(v[a],v[b]);
			}
			else
			{
				scanf("%d",&a);
				v[a]=k;//标记转移到的位置,
				per[k]=k;//把这个位置处保存上他的操作编号(下标)
				++k;//k指向下一个空白处
			}
		}
		int cnt=0;
		for(i=0;i<n;++i)
		{
			int temp=find(v[i]);//找根节点
			if(!mark[temp])//如果根节点没出现过
			{
				mark[temp]=1;//标记
				++cnt;//累加
			}
		}
		printf("Case #%d: %d\n",++t,cnt);//按格式输出
	}
	return 0;
}





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值