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
#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; }
不知道我講的明白了沒,如果大家還沒理解,可以看看這個博客,暢通工程並查集詳解。