亲友团问题
时间限制(普通/Java) : 1000 MS/ 3000 MS 运行内存限制 : 65536 KByte
题目描述
在2014“华为杯”南邮大学生团体歌唱大赛每一个轮比赛现场,众多亲友团是一道亮丽的风景,他(她)们或来助威、或来观摩、或来刺探对手情报,不同亲友团之间偶尔还起冲突。为避免安全问题,主办方在赛场会划分许多独立区域,每一个区域安置一个亲友团,现在请你根据主办方提供的比赛现场信息,预测至多需要划分出多少个独立区域。
我们将主办方提供的比赛现场信息进行简化,从1开始按顺序给进入比赛现场的每位亲友分配一个编号,依次为1、2、...、K,K为亲友总数,为保护隐私,主办方只能告诉你M组两个不同亲友属于同一亲友团信息,这些信息有可能重复。
输入
输入包括多个测试用例,首先给出测试用例数N,接着给出N个测试用例,1≤N≤100。
每一个测试用例包括M+1行,首先给出两个整数K和M;再依次给出M行,每行给出两个不同正整数i和j,表示两个不同亲友i和j属于同一亲友团,i≠j,1≤i≤K,1≤j≤K,,1≤K≤10000,1≤M≤10000。
输出
输出包括多行,对于每个测试用例,输出一行,给出至多需要划分出的独立区域数量。
样例输入
2
3 1
1 2
3 2
1 2
2 3
样例输出
2
1
解题思路一
开一个数组,把属于一个亲友团的亲友赋一个相同的值,最后判断有几种不同的值就有几个不同的组。但是可能出现的问题就是:假设1 2属于一个团赋值为a,3 4属于一个团赋值为b,再1 3属于一个团赋值为c;
那么 1 2 3 4
c a c b,分别对应的值如前面所示,得到的结果就是3个区域,但实际上只要1个区域就够了。
(如果在遇到1 3两个亲友都已经有所属的团时,就需要把其中一个亲友以及他所属的团的所有亲友的值都改为另一个亲友的值,这样时间复杂度太高)
这种思路无法解决问题。
解题思路二
首先给数组中每个元素赋值为其下标的值。当i == a[i]表示i为该团的团长;若i != a[i]表示i属于某个团。
统计结束后经过一次遍历,若i==a[i],则区域数加1。最终得出答案。
这个思路关键在于给出的两个亲友是否已经有所属团。
如给出p,q两个亲友
若p,q都没有所属团,则a[p]=p;a[q]=a[p];
若p有q没有 ,则a[q]=a[p];
若q有p没有 ,则a[p]=a[q];
若p,q都有所属团 ,则需要找出p所属团的团长,并把团长的值改为q所属团的团长的值。
while(p != a[p]) { p = a[p]; }while(q != a[q]){ q = a[q];}a[p] = a[q];
完整代码:
1 #include <cstdio> 2 #include <iostream> 3 #include <string.h> 4 #include <cmath> 5 using namespace std; 6 7 const int maxn=10010; 8 int p[maxn]; 9 10 int main() 11 { 12 int t; 13 scanf("%d", &t); 14 while(t--) 15 { 16 int k, m; 17 scanf("%d%d", &k, &m); 18 memset(p, 0, sizeof(p)); 19 for(int i=0; i<m; ++i) 20 { 21 int a, b; 22 scanf("%d%d", &a, &b); 23 if(p[a]==0 && p[b]==0) 24 { 25 p[a]=a; 26 p[b]=p[a]; 27 } 28 else 29 { 30 if(p[a]==0 && p[b]!=0) 31 { 32 p[a]=p[b]; 33 } 34 else 35 { 36 if(p[a]!=0 && p[b]==0) 37 { 38 p[b]=p[a]; 39 } 40 else 41 { 42 if(p[a] != p[b]) 43 { 44 while(p[a] != a) 45 { 46 a = p[a]; 47 } 48 while(p[b] != b) 49 { 50 b = p[b]; 51 } 52 p[a] = p[b]; 53 } 54 } 55 } 56 } 57 } 58 int ans = 0; 59 for(int i=1; i<=k; ++i) 60 { 61 if(p[i]==i || p[i]==0) 62 { 63 ++ans; 64 } 65 } 66 printf("%d\n", ans); 67 } 68 return 0; 69 }
解题思路三
并查集(Union-find sets):是一种简单的用途广泛的集合。并查集是若干个不相交的集合,能够较快的合并和判断元素所在集合。相关应用有求无向图的连通分量个数,最小
公共祖先,带限制的作业排序,以及Kruskar算法求最小生成树。
基本做法和思路二相似,只是加入了类似权值的数组,每次将两个团合并入权值较大的那个团。
1 #include <stdio.h> 2 #include <iostream> 3 #include <string.h> 4 #include <cmath> 5 using namespace std; 6 7 const int maxn=10005; 8 int p[maxn]; 9 int r[maxn]; 10 11 int main() 12 { 13 int t; 14 scanf("%d", &t); 15 while(t--) 16 { 17 int k, m, count=0; 18 scanf("%d%d", &k, &m); 19 memset(r, 0, sizeof(r)); 20 for(int i=1; i<maxn; ++i) p[i]=i; 21 for(int i=0; i<m; ++i) 22 { 23 int a, b; 24 scanf("%d%d", &a, &b); 25 while(a != p[a]) 26 { 27 a = p[a]; 28 } 29 while(b != p[b]) 30 { 31 b = p[b]; 32 } 33 if(a == b) continue; 34 if(r[a] > r[b]) 35 { 36 p[b] = a; 37 } 38 else 39 { 40 p[a] = b; 41 if(r[a] == r[b]) 42 { 43 r[b]++; 44 } 45 } 46 } 47 int ans = 0; 48 for(int i=1; i<=k; ++i) 49 { 50 if(p[i]==i) 51 { 52 ++ans; 53 } 54 } 55 printf("%d\n", ans); 56 } 57 return 0; 58 }