合纵连横
时间限制:1000 ms | 内存限制:65535 KB
难度:3
- 描述
-
乱世天下,诸侯割据。每个诸侯王都有一片自己的领土。但是不是所有的诸侯王都是安分守己的,实力强大的诸侯国会设法吞并那些实力弱的,让自己的领土面积不断扩大。而实力弱的诸侯王为了不让自己的领土被吞并,他会联合一些其他同样弱小的诸侯国,组成联盟(联盟不止一个),来共同抵抗那些强大的诸侯国。 强大的诸侯国为了瓦解这些联盟,派出了最优秀的间谍来离间他们,使一些诸侯国退出联盟。最开始,每个诸侯国是一个联盟。有两种操作
1、U x y 表示x和y在同一个联盟。(0≤x,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; }
版权声明:本文为博主原创文章,未经博主允许不得转载。