并查集基础练习

1.团伙(group.pas/c/cpp)

AYYZOJ p1948

题目分析:

题目大意是有n个人求最多可能有几个团伙,由题意很容易想到用并查集解决。我们可以先假设每个人都不认识,在这一前提下可以保证团伙最多。在处理朋友的时候,把所有是朋友的人加入同一团伙;处理两人是敌人的时候,需要事先记录下每个人的敌人,然后依次把敌人的敌人加入同一团伙。最后统计有多少个团伙即可。

 1 var d:array[0..1000,0..1000] of longint;  //d[i][j]存储第i个人的第j个敌人
 2     f:array[0..1000] of longint;
 3     i,j,m,n,x,y,k,fx,fy,ans:longint;
 4 function get_father(x:longint):longint;
 5 begin
 6  if f[x]=0 then
 7   begin
 8    get_father:=x;
 9   end      else
10   begin
11    get_father:=get_father(f[x]);
12    f[x]:=get_father;
13   end;
14 end;
15 
16 begin
17 fillchar(f,sizeof(f),0);
18   fillchar(d,sizeof(d),0);
19  readln(n,m);
20  for i:=1 to m do
21   begin
22    readln(k,x,y);
23    fx:=get_father(x); fy:=get_father(y);
24    if k=0 then                            //如果x,y是朋友则合并。
25     begin
26      if fx<>fy then f[y]:=fx;
27     end;
28    if k=1 then
29     begin
30      inc(d[x,0]); d[x,d[x,0]]:=y;            //d[x,0]记录x的敌人数
31      inc(d[y,0]); d[y,d[y,0]]:=x;            //d[y,0]记录y的敌人数
32 //如果x,y是敌人,则把x并入y的所有敌人中,y并入x的敌人中
33      for j:=1 to d[y,0] do
34       if fx<>get_father(d[y,j]) then f[d[y,j]]:=fx;        
35      for j:=1 to d[x,0] do
36       if fy<>get_father(d[x,j]) then f[d[x,j]]:=fy;
37     end;
38   end;
39 ans:=0;
40  for i:=1 to n do
41   if f[i]=0 then inc(ans);            //如果自己就是树的根表明是一个集合
42 writeln(ans);
43 end.
参考程序1
 1 var
 2  a:array[1..1000,0..1000]of longint;
 3  i,j,k,n,m,t,s:longint;
 4  fa:array[1..1000]of longint;
 5 function getfa(x:longint):longint;
 6 begin
 7  if fa[x]=0 then getfa:=x
 8             else
 9              begin
10               fa[x]:=getfa(fa[x]);
11               getfa:=fa[x];
12              end;
13 end;
14 procedure union(x,y:longint);
15 var i,j:longint;
16 begin
17  i:=getfa(x);
18  j:=getfa(y);
19  if i<>j then fa[i]:=j;
20 end;
21 begin
22  readln(n,m);
23  for i:=1 to m do
24  begin
25   readln(j,k,t);
26   if j=0 then union(k,t)
27           else begin
28                 inc(a[k,0]);
29                 a[k,a[k,0]]:=t;
30                 inc(a[t,0]);
31                 a[t,a[t,0]]:=k;
32                end;
33  end;
34  for i:=1 to n do
35   for j:=1 to a[i,0] do
36    union(a[i,1],a[i,j]);
37  for i:=1 to n do
38   if getfa(i)=i then inc(s);
39   writeln(s);
40 end.
参考程序2

我比较倾向于参考程序2,程序1有点不理解。

2.打击犯罪(black.pas/c/cpp)

AYYZOJ p1949

分析:

因为打击犯罪的顺序为由前到后依次打击,很容易想到可以倒序合并(为什么我想不到,太弱了。。),一旦某一团伙的危险程度>n/2,停止输出即可。

至于团伙的危险等级的确认,可以新建一个count数组,在合并时累加即可。

下面分析为转载:来源

此题首先要理解题意,题意意思是按顺序删点,如果删掉k点后,1~k-1的点也被删掉这样就好解决了,那怎么和并查集联系起来呢?我们可以这样理解,我从1开始删点如当前删了点k这对大于k的点进行并查集,并得时候更新根节点的节点数,如果最大的节点数大于(n+1)/2则继续删点,直到最大集合节点数小于等于(n+1)/2为止,但这样做每删一个点,并查集就得重新计算一次,那我们换一个思路,我们倒过来,我们不删点,我们求删点完后剩下的图组成的集合,我假设删点删到了k那剩下的图就是由k~n之间的的点组成如果他们组成的结合最大子图节点数不大(n+1)/2则说明k不该删,则继续往前推。

 1 var
 2   i,j,k,l,m,n:longint;
 3   f,c:array[1..1000] of longint;      //count数组
 4   g:array[1..1000] of boolean;
 5   a:array[0..1000,0..1000] of longint;
 6 function getfa(x:longint):longint;
 7 begin
 8   if f[x]=0 then exit(x);
 9   f[x]:=getfa(f[x]);
10   getfa:=f[x];
11 end;
12 procedure together(x,y:longint);
13 var i,j:longint;
14 begin
15   i:=getfa(x);
16   j:=getfa(y);
17   if i<>j then
18   begin
19     f[i]:=j;
20     c[j]:=c[i]+c[j];
21     c[i]:=0;
22   end;
23 end;
24 begin
25   readln(n);
26   for i:=1 to n do c[i]:=1;
27   for i:=1 to n do
28    begin
29      read(a[i,0]);
30      for j:=1 to a[i,0] do read(a[i,j]);
31    end;
32   for i:=n downto 1 do
33    begin
34      for j:=1 to a[i,0] do
35       if a[i,j]>i then
36        together(i,a[i,j]);
37      l:=0;
38      for j:=1 to n do if c[j]>n div 2 then l:=1;
39      if l=1 then
40       begin
41         writeln(i);
42         break;
43       end;
44    end;
45 end.
参考程序
原文地址:https://www.cnblogs.com/vacation/p/5418551.html