1、題目要求:

某學校有N個學生,形成M個俱樂部。每個俱樂部裡的學生有著一定相似的興趣愛好,形成一個朋友圈。一個學生可以同時屬於若干個不同的俱樂部。根據“我的朋友的朋友也是我的朋友”這個推論可以得出,如果A和B是朋友,且B和C是朋友,則A和C也是朋友。請編寫程式計算最大朋友圈中有多少人。

輸入格式:

輸入的第一行包含兩個正整數N(≤30000)和M(≤1000),分別代表學校的學生總數和俱樂部的個數。後面的M行每行按以下格式給出1個俱樂部的資訊,其中學生從1~N編號:

第i個俱樂部的人數Mi(空格)學生1(空格)學生2 … 學生Mi

輸出格式:

輸出給出一個整數,表示在最大朋友圈中有多少人。

輸入樣例:

7 4
3 1 2 3
2 1 4
3 5 6 7
1 6
 
結尾無空行

輸出樣例:

4
 
結尾無空行
 
2、題目解析:
首先我們來理清一下題目意思,A和B是朋友,B和C是朋友,那麼A和C也是朋友,如果此時C又和D是朋友,那麼A和D也是朋友,則A此時與B,C,D都是朋友,那麼這個以A為起點
的朋友圈就有4個人。又如測試用例,第一行表示第一個圈子,有1,2,3這3個人,第二個圈子有1,4這兩個人,因為1和4是好朋友,所以4和2、3都是朋友,因此以1為中心的這個
朋友圈共有4個人。這是一個以朋友為傳遞關係建立成的朋友圈,是一種連通性的問題。簡單來說就是求這幾個連通分支裡點最多的連通分支,朋友圈代表連通分支,朋友圈裡
的人代表連通分支上的點。所以題目就是求以朋友關係連起來的最大的朋友圈(連通分支)裡有幾個人(點)。
 
3、程式碼:
#include <bits/stdc++.h>
using namespace std;
#define MAXn 30001
int fa[MAXn];
int num[30001]; int Find(int x){
int r=x;
while(r!=fa[r])
r=fa[r];
int i=x,temp;
while(r!=fa[i]){
temp=fa[i];
fa[i]=r;
i=temp;
}
return r;
} void Union(int x,int y){
int fx=Find(x),fy=Find(y);
if(fx!=fy){
fa[fx]=fy;
num[fy]+=num[fx];
}
} int main(){
int n,m,people,first,p;
cin>>n>>m;
for(int i=0;i<=n;i++)
fa[i]=i;
for(int i=0;i<=n;i++)
num[i]=1;
while(m--){
cin>>people;
cin>>first;
while(--people){
cin>>p;
Union(first,p);
}
}
int max=0;
for(int i=0;i<=n;i++){
if(num[i] > max)
max=num[i];
}
cout<<max<<endl;
return 0;
}

4、“並查集” 說明:

首先本題是屬於資料結構中的 “並查集“ 型別的題目,所以要理解本題的程式碼,我們就要先弄懂什麼是並查集。

所以現在,我們先來搞懂 ”並查集“是什麼?

顧名思義,”並“就是合併,”查“就是查詢,”集“就是集合(元素唯一)。”並查集"主要程式碼結構有兩個函式,一個主函式“,一個函式是 Find(x) ,一個是 Union(x,y)。

將每個集合看成每棵樹,用陣列 fa[]  來儲存各個節點的父節點,fa[i] 表示 i 的父節點。如下圖1,fa[2] = 1,fa[3] = 1,fa[4] = 2......  (圖論基礎)

若每棵樹只有一個節點,那麼它的父節點就是它自己,即 fa[i] = i,誰和誰都沒有關係,如圖2。

 合併:Union(x,y)

那麼我們要怎麼把它們建立聯絡呢,就是把單獨的樹合併成一顆大的樹?

可以令每棵樹的根節點都一樣,那麼它們就合併了,比如要將 圖1中的1 和2 合併成一顆樹,可以讓1成為2的根節點(當然也可以讓2成為1的根節點),

即 fa[2] = 1,fa[1] = 1,(fa[1] = 2,fa[2] = 2)這樣就變成了圖3.

若要將3也合併到1那顆樹上,如圖.可以知道fa[3] = 2, fa[2] = 1,fa[1] = 1,則把3合併過來的表示式:fa[3] = fa[1],

假設x=2,y=3 ,將2和3合併,則 fa[x] = y ;

這就是將集合合併起來,從上面兩個圖中我們可以發現若1都是根節點,那麼fa[1] = 1,若2是根節點,那麼fa[2] = 2,即fa[i] = i 表示此時 i 是這顆樹的根節點,

”查“就是查詢根節點。

找到根節點有什麼用?

查詢:Find(x)

找到一顆樹的根節點就相當於找到了這顆樹的編號,換個說法說就是某個集合的編號,可以知道這個人在哪個集合裡。

如下圖

我們想找5屬於哪顆樹,就得一個一個父節點網上找,直到找到根節點 Find(5) = 1才知道5屬於上面那顆樹。如果我們想找8,那麼我們必須找到8的根節點 Find(8) = 6

才知道8屬於下面那棵樹。

因此函式 Find(x) 就是查詢 x的根節點,返回值是一個數(根節點)。

如果我想讓5和6成為好朋友呢?只需要在5和6之間畫一條線連起來,或者之間讓6的父節點之接變成1,這樣6就屬於5的那顆樹啦,如下圖

或者這樣也行,

即讓5的根節點等於6的根節點,因此合併的原理就是:查詢x和y 的根節點,fx = Find(x) , fy = Find(y) ;令y的根節點等於x的根節點,Find(fx) = fy。

說到這,想必都清楚”並“和”查“的意思了吧?不知道我說得清楚了沒?文案和圖有點亂。。。。

還有一點要注意的是,還有個"路徑壓縮",這個 “路徑壓縮” 是啥捏?

舉個例子:如下圖

假設1是根節點,(不分是否二叉樹), 如果我們要找3的根節點是誰,那麼上面圖的做法是 fa[3] = 1,查詢一次就找到了。而下面圖的做法是 fa[3] = 2,不是根節點(怎麼判斷是不是根節點上文有說),

再查詢 fa[2] = 1 找到了,是不是上面圖的做法查詢的速度會更快一點?如果有n個節點,那麼下面圖要查詢n-1次了,此時效率比較低。所以“路徑壓縮” 就是要把樹(集合)上的

節點都搞成上面圖的樣子,這樣就縮短了查詢根節點的路徑了。

5、程式碼解析:

瞭解了“並查集 " ,那麼我們現在就來看看本題的程式碼怎麼實現吧!

#include <bits/stdc++.h>
using namespace std;
#define MAX 30001
int fa[MAX];//儲存父節點
int num[30001];//儲存節點數 /*查詢根節點*/
int Find(int x){
//找x的根節點
int r=x;//x賦給r,以免中間發生變化後x改變
while(r!=fa[r])//迴圈直到找到根節點,上文第4點有說例子
r=fa[r];//讓r的父節點等於r,直到找到x的根節點,此時就可返回r了,下面是為了壓縮路徑寫的程式碼
//路徑壓縮
int i=x,temp;//temp為中間變數
while(r!=fa[i]){//假設鏈式樹,“1---2---3---4” 根節點r=1,x=3,即要把3直接套在1後面,這裡上文“路徑壓縮”有說到。
temp=fa[i];//把3的父節點2賦給temp
fa[i]=r;//讓3的父節點等於1
i=temp;//把3的父節點2賦給i,繼續迴圈把2的根節點變成1
}
return r;//最後返回根節點r
} /*合併兩個集合*/
void Union(int x,int y){
int fx=Find(x),fy=Find(y);//分別找到x和y的根節點
if(fx!=fy){//如果x和y的根節點不一樣,說明兩個人不在一棵樹上,此時就要合併兩個人
fa[fx]=fy;//讓y的根節點fy成為x的根節點,兩棵樹(集合)就合併了
num[fy]+=num[fx];//fy這顆樹多加num[fx]個人,num[fx]代表以fx為根節點所在的樹上有幾個節點
}
} int main(){
int n,m,people,first,p;
cin>>n>>m;
//給每個人編號,並讓每個人的父節點都是自己
for(int i=0;i<=n;i++)
fa[i]=i;
//定義一個num[]陣列來儲存第i棵樹(朋友圈)上有幾個節點(人),剛開始都是一個人,所以初始化為1
for(int i=0;i<=n;i++)
num[i]=1;
//輸入資料
while(m--){
cin>>people;//每一行中第一個數是這個圈子有幾個人
cin>>first;//第二個數在這裡輸入,是為了將第一個數作為每一行(每一個圈子)的根,用來標識朋友圈加以區別
while(--people){
cin>>p;//依次輸入每個人的編號
Union(first,p);//每輸入一個人就合併到第一個人的樹(圈子)上,這樣每一行的人都代表一個圈子裡的人
}
}
//求出最大的樹(圈子)有幾個節點(人)
int max=0;
for(int i=0;i<=n;i++){//人的編號從1~N
if(num[i] > max)
max=num[i];
}
cout<<max<<endl;
return 0;
}

不知道我講的明白了沒,如果大家還沒理解,可以看看這個部落格,暢通工程並查集詳解